How to tell a script which row to start output - google-apps-script

I am using a script to output the date and time that a row was last updated on a Google Sheet. It seems to work just fine, but I want it to only output beginning at the second row, since my first row is a header row full of labels for the columns. I can't seem to figure it out.
The script is from here: https://www.wikihow.com/Google-Sheets-How-to-Insert-Time-in-Cell-Automatically#Script-Editor
This is how it looks in my Apps Script:
/** #OnlyCurrentDoc */
function onEdit(e){
const sh = e.source.getActiveSheet();
sh.getRange ('A' + e.range.rowStart)
.setValue (new Date())
.setNumberFormat ('MM/dd/yyyy HH:MMam/pm');
}
I think I need to define rowStart, but I'm not sure how to do that and search engines haven't pulled up answers I understand.
I tried appending rowStart(2) and rowStart > 0 in place of the original rowStart, which produced errors and made the whole script stop working. I also tried the below with the same response.
rowStart(
value : 2
) : 2;
From here: https://help.grapecity.com/spread/SpreadJSWeb/JavascriptLibrary~GcSpread.Sheets.PrintInfo~rowStart.html
I am new to Google Apps Script (and any scripting), though a longtime Excel and Sheets user. So please explain it to me like I am five. 😅 Branching out!

OK, so this script, as it says on the original wikihow page, will:
insert a timestamp into the specified column any time you enter data into a cell, in the same row as the data you entered. For instance, if you type something into cell A2, a timestamp will appear in cell M2.
So essentially the way that script works, at least per my reading:
It is defining an onEdit handler which handles an edit event every time a user makes any change to any cell in the spreadsheet
the edit event (called e in the script) has a bunch of information about the edit on it, including the row number of the first row that was edited (e.range.rowStart)
the script contains a hard-coded column letter (in your case, A, in the example on wikihow they used M)
every time any edit is made , it goes to the cell identified by the hardcoded column letter and the 1st row that was edited, and it inserts the current date into that cell.
First of all, since this is JavaScript, you can paste your code into a text editor that supports JavaScript, like VSCode for example, and ask it to format it for you. This might bring some clarity to the code because the text editor knows what the syntax means, so it can give you some hints about whats going on or at very least it can make it look nicer:
/** #OnlyCurrentDoc */
function onEdit(e) {
const sh = e.source.getActiveSheet();
sh.getRange('A' + e.range.rowStart)
.setValue(new Date())
.setNumberFormat('MM/dd/yyyy HH:MMam/pm');
}
Second of all, single letter variable names and abbreviations are often frowned upon because they can prevent us from knowing what's going on. And sometimes code can be clarified by giving names to things that might otherwise not be obvious at all. So I will do some naming in this code:
/** #OnlyCurrentDoc */
function onEdit(editEvent) {
const sheet = editEvent.source.getActiveSheet();
const rowThatWasEdited = editEvent.range.rowStart;
const columnThatHoldsTheLastEditedDates = 'A';
const correspondingCell = columnThatHoldsTheLastEditedDates + rowThatWasEdited;
const rightNowDate = new Date();
sheet.getRange(correspondingCell)
.setValue(rightNowDate)
.setNumberFormat('MM/dd/yyyy HH:MMam/pm');
}
Finally, since this is javascript, we have access to all of the javascript features like if statements, loops, functions, etc. Since you want to make it ignore the header row, I would recommend an if statement: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else
if we are on the header row, do nothing. Otherwise, do the normal thing.
/** #OnlyCurrentDoc */
function onEdit(editEvent) {
const sheet = editEvent.source.getActiveSheet();
const rowThatWasEdited = editEvent.range.rowStart;
if(rowThatWasEdited > 1) {
const columnThatHoldsTheLastEditedDates = 'A';
const correspondingCell = columnThatHoldsTheLastEditedDates + rowThatWasEdited;
const rightNowDate = new Date();
sheet.getRange(correspondingCell)
.setValue(rightNowDate)
.setNumberFormat('MM/dd/yyyy HH:MMam/pm');
}
}

Related

Static timestamping in Google Sheets

I am trying to add STATIC timestamp to my data whenever it is imported or pasted in the sheets.
I am using this formula now
(=ARRAYFORMULA( IFS(I1:I="","",L1:L="",NOW(),TRUE,L1:L)))
but, whenever I open the sheet again the time gets changed automatically to the current time as i am using the now() function. I tried on-Edit in the script, but it's only working when the data is manually entered.
Is there any other way I can use to static timestamp when data is being pasted or imported?
Instead of NOW() on the formula, do it via script using new Date().
The NOW() function updates the timestamp every time the spreadsheet is open or something changes in it, while the new Date() gives you a full (date and time) and static timestamp.
Also, as I've seen on the comments of your question, there really is no way to use onEdit() through automated scripts and macros.
Answer
You can use a custom function to return the actual date with the method new Date() and the Properties Service. Open Apps Script and paste the following function:
Code
function getTimestamp(reset) {
// update the timestamp
if (reset == 1) {
setTime()
}
// try-catch structure in order to set the time in the first execution
try {
var time = ScriptProperties.getProperty('time')
}
catch (err) {
setTime()
var time = ScriptProperties.getProperty('time')
}
return time
}
function setTime() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var time = new Date()
ScriptProperties.setProperty('time', time)
}
How it works
Now, you can use it in any cell like another Sheet function. Call the function with =getTimestamp(0). On the first execution, it tries to get the saved property time, but as the property does not exist it generates a timestamp and saves a new property in the project with the key time and the value of the timestamp.
In the following executions, the value obtained by the function when it is recalculated is the same, since the property is not overwritten unless the function is called with a 1 input: =getTimestamp(1). In this case, the timestamp is updated, but if it is not set back to =getTimestamp(0), every time the function is recalculated (which happens automatically every so often) the timestamp will change.
In conclusion, always use =getTimestamp(0). When you want to update the value, change it to =getTimestamp(1) and go back to the original formula.
update
I have updated the answer to explain how to update the timestamp when new values are added:
Use a cell as input to the function, e.g. =getTimeStamp(A1) 2.
Create an onEdit trigger
Check that the range of the e event belongs to new values.
Update the value of A1 to 1 and then to 0 if you have detected new values.
example:
function onEdit(e){
var range = e.range
var cell = SpreadsheetApp.getActiveSpreadsheet().getRange('A4')
if (range.columnStart > 1 && range.rowStart > 10){
cell.setValue(1)
SpreadsheetApp.flush()
cell.setValue(0)
}
}
If new values are added from column 1 and row 10, A1 is updated to 1 and then to 0, thus updating the value of the timeStamp function and saving it permanently until the trigger is executed again.
References:
Custom Functions in Google Sheets
Working with Dates and Times
Apps Script: Extending Google Sheets
Properties Service
Not sure have your question got a solution. I had the same struggle as yours over the year, especially with pasted data, and I found a solution that works for my case nicely (but not by formula, need to run in Apps Script).
Some background for my case:
I have multiple sheets in the spreadsheet to run and generate the
timestamp
I want to skip my first sheet without running to generate timestamp
in it
I want every edit, even if each value that I paste from Excel to
generate timestamp
I want the timestamp to be individual, each row have their own
timestamp precise to every second
I don't want a total refresh of the entire sheet timestamp when I am
editing any other row
I have a column that is a MUST FILL value to justify whether the
timestamp needs to be generated for that particular row
I want to specify my timestamp on a dedicated column only
function timestamp() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const totalSheet = ss.getSheets();
for (let a=1; a<totalSheet.length; a++) {
let sheet = ss.getSheets()[a];
let range = sheet.getDataRange();
let values = range.getValues();
function autoCount() {
let rowCount;
for (let i = 0; i < values.length; i++) {
rowCount = i
if (values[i][0] === '') {
break;
}
}
return rowCount
}
rowNum = autoCount()
for(let j=1; j<rowNum+1; j++){
if (sheet.getRange(j+1,7).getValue() === '') {
sheet.getRange(j+1,7).setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss");
}
}
}
}
Explanation
First, I made a const totalSheet with getSheets() and run it
with a for loop. That is to identify the total number of sheets
inside that spreadsheet. Take note, in here, I made let a=1;
supposed all JavaScript the same, starts with 0, value 1 is to
skip the first sheet and run on the second sheet onwards
then, you will notice a function let sheet = ss.getSheets()[a]
inside the loop. Take note, it is not supposed to use const if
your value inside the variable is constantly changing, so use
let instead will work fine.
then, you will see a function autoCount(). That is to make a for
loop to count the number of rows that have values edited in it. The
if (values[i][0] === '') is to navigate the script to search
through the entire sheet that has value, looking at the row i and
the column 0. Here, the 0 is indicating the first column of the
sheet, and the i is the row of the sheet. Yes, it works like a
json object with panda feeling.
then, you found the number of rows that are edited by running the
autoCount(). Give it a rowNum variable to contain the result.
then, pass that rowNum into a new for loop, and use if (sheeet.getRange(j+1,7).getValue() === '') to determine which row
has not been edited with timestamp. Take note, where the 7 here
indicating the 7th column of the sheet is the place that I want a
timestamp.
inside the for loop, is to setValue with date in a specified
format of ("yyyy-MM-dd hh:mm:ss"). You are free to edit into any
style you like
ohya, do remember to deploy to activate the trigger with event type
as On Change. That is not limiting to edit, but for all kinds of
changes including paste.
Here's a screenshot on how it would look like:
Lastly, please take note on some of my backgrounds before deciding to or not to have the solution to work for your case. Cheers, and happy coding~!
You cannot get a permanent timestamp with a spreadsheet formula, even with a named function or an Apps Script custom function, because formula results refreshed from time to time. When the formula gets recalculated, the original timestamp is lost.
The easiest way to insert the current date in a cell is to press Control + ; or ⌘;. See the keyboard shortcuts help page.
You can also use an onEdit(e) script to create permanent timestamps. Search this forum for [google-apps-script] timestamp to find many examples.

I have a pseudocode, but can't code yet

thank you and sorry for my incredibly unexperienced question in advance. So, I want to make a code and I know what I want it to do, I just don't know how to program. What I need is:
function GenPre()
1.- delete range Presupuesto!A12:C42
2.- copy range Imp!A2:Imp!C33 VALUES in Presupuesto!A12:Presupuesto!C42 (Imp cells are formulas, and I want to copy just the values)
3.- show only used rows in column A in Presupuesto!A12:A42 (consider some rows will be already hidden, so unhiding them first would be an idea)
4.- go to sheet Presupuesto (once I do this function, I want to end up on the sheet Presupuesto
end Generar
This function will be runned by a button in another sheet in the same spreadsheet.
and so far, I have this:
function GenPre() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetbyname(Presupuesto);
//next step is to select and delete the content of the range on the sheet
}
I know I'm asking for much, I just can't find much about selecting defined cells... and I really don't know how to program yet.
Thanks a bunch!!
Edit
So, I started tweaking with what k4k4sh1 answered and got this (AND reading other posts on hiding rows containing "x" on a given cell):
function GenPre() {
var sheetp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto') //name a variable to the sheet where we're pasting information
var sheetc = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Imp') //name a variable to the sheet frome where we're copying information
sheetp.getRange('a12:c41').clearContent() //delete all values in the range where we're copying
sheetc.getRange('A2:C31').copyValuesToRange(sheetp,1,3,12,41); //copy from source range to destination range
sheetp.showRows(12,41); //make sure all rows in the destination range are shown
for( i=12 ; i<=41 ; i++) {
if (sheetp.getRange('A'+i).getValue() == '') { // status == ''
sheetp.hideRows(i);
}
}
}
Te script is running how it should, but now, I want it to run faster (takes 12 seconds to run, when it doesn't really look that heavy), and is there a function to switch my view to sheetp? thank you all!
You're asking us to do all the work :)
Let's start from your piece of code:
the method .getSheetByName(shName) accepts a string as argument, so you should change it to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Presupuesto');.
Mind that JavaScript is case-sensitive, so .getSheetbyname is not the same as .getSheetByName().
According to Sheet Class Reference use sheet.getRange() to get your Range Object. Take a look to Range Class Reference: to clear the range content including formats use .clear(), to clear just the content leaving the formatting intact use .clearContent().
To hide unused rows try:
function hideRows(sheetName, column) {
var s = SpreadsheetApp.getActive().getSheetByName(sheetName);
s.showRows(1, s.getMaxRows());
s.getRange(column)
.getValues()
.forEach(function (r, i) {
if (r[0] == '') {s.hideRows(i + 1);}
});
}
// hideRows('Presupuesto', 'A12:A42');

How to evaluate a concatenated formula in Google Sheets? [duplicate]

In a Google Docs spreadsheet, I'm looking for something like =EVAL(A1) where A1 is set to "=1+2".
I found out that in MS Excel there is an EVALUATE() function (which seems a bit tricky to use properly). But I could not find anything similar in Google Docs.
I also searched through the function list, but could not find anything helpful...
No, there's no equivalent to Excel's EVALUATE() in Google Sheets.
There's long history behind this one, see this old post for instance.
If you're just interested in simple math (as shown in your question), that can be done easily with a custom function.
function doMath( formula ) {
// Strip leading "=" if there
if (formula.charAt(0) === '=') formula = formula.substring(1);
return eval(formula)
}
For example, with your A1, put =doMath(A1) in another cell, and it will be 3.
I know this an old post. I'm just wondering, why nobody suggested:
myCell.getValue();
This will give you the result of the formula in myCell (3 in your example).
If you want to write the result to the cell (instead of the formula), you could use:
function fixFormula(myCell) {
myCell.setValue(myCell.getValue());
}
Short answer
As was mentioned previously, Google Sheets doesn't have a built-in EVALUATE function, but Google Sheets could be extended to add this function. Fortunately some SocialCalc files could be used to make this easier.
Script
On Google spreadsheet I'm sharing my progress. At this time I added the SocialCalc files that I think that are required and a couple of functions, and several test cases.
NOTES:
Google Sheets specific functions like FILTER, UNIQUE, among others are not available in SocialCalc as well as other functions like SIGN.
I think that the SocialCalc file should be replaced by those on https://github.com/marcelklehr/socialcalc as it looks to be updated recently. H/T to eddyparkinson (see https://stackoverflow.com/a/16329364/1595451)
Uses
The EVALUATE function on the linked file could be used as a custom function.
Example 1
A1: '=1+2 (please note the use of an apostrophe to make the formula be treated by Google Sheets as a string.
B1 formula:
=EVALUATE(A1)
B1 display value:
3
Example 2
To "EVALUATE" a formula like =VLOOKUP(2,A1:B3,2), at this time we need to use the "advanced" parameters. See the following example:
B1: '=VLOOKUP(2,A1:B3,2)
C1 formula:
=EVALUATE(B1,"data","A1:B3")
C1 display value:
B
Code.gs
/**
*
* Evaluates a string formula
*
* #param {"=1+1"} formula Formula string
* #param {"Tests"} sheetName Target sheet.
* #param {"A1"} coord Target cell.
*
* #customfunction
*
*/
function EVALUATE(formula,sheetName,coord){
// SocialCalc Sheet object
var scSheet = new SocialCalc.Sheet();
if(sheetName && coord){
// Pass values from a Google sheet to a SocialCalc sheet
GS_TO_SC(scSheet,coord,sheetName);
}
var parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(formula.substring(1));
var value = SocialCalc.Formula.evaluate_parsed_formula(parseinfo,scSheet,1); // parse formula, allowing range return
if(value.type != 'e'){
return value.value;
} else {
return value.error;
}
}
/**
*
* Pass the Google spreadsheet values of the specified range
* to a SocialCalc sheet
*
* See Cell Class on socialcalc-3 for details
*
*/
function GS_TO_SC(scSheet,coord,sheetName){
var ss = SpreadsheetApp.getActiveSpreadsheet();
if(sheetName){
var sheet = ss.getSheetByName(sheetName);
var range = sheet.getRange(coord);
} else {
var range = ss.getRange(coord);
}
var rows = range.getNumRows();
var columns = range.getNumColumns();
var cell,A1Notation,dtype,value,vtype;
// Double loop to pass cells in range to SocialCalc sheet
for(var row = 1; row <= rows; row++){
for(var column = 1; column <= columns; column++){
cell = range.getCell(row,column);
A1Notation = cell.getA1Notation();
value = cell.getValue();
if(cell.isBlank()){
dtype = 'b';
vtype = 'b';
} else {
switch(typeof value){
case 'string':
dtype = 't';
vtype = 't';
break;
case 'date':
case 'number':
dtype = 'v'
vtype = 'n';
break;
}
}
scSheet.cells[A1Notation] = {
datavalue: value,
datatype: dtype,
valuetype: vtype
}
}
}
}
formula1.gs
https://github.com/DanBricklin/socialcalc/blob/master/formula1.js
socialcalcconstants.gs
https://github.com/DanBricklin/socialcalc/blob/master/socialcalcconstants.js
socialcalc-3.gs
https://github.com/DanBricklin/socialcalc/blob/master/socialcalc-3.js
If you want to evaluate simple math(like A1: "(1+2)*9/3"), you can use query:
=query(,"Select "&A1&" label "&A1&" ''",0)
Basic math sent to query's select is evaluated by query.
Copy and paste the formulas:
Maybe you can copy and paste the formulas you need from "jQuery.sheet". Moved to:
https://github.com/Spreadsheets/WickedGrid
Looks to be all "open source"
Wont fix the issue
Also: The issue "Enable scripts to use standard spreadsheet functions" is marked as "Wont fix", see https://code.google.com/p/google-apps-script-issues/issues/detail?id=26
Ethercalc
there is a google like opensource spreadsheet called Ethercalc
GUI Code:
https://github.com/audreyt/ethercalc
Formulas: https://github.com/marcelklehr/socialcalc
Demo - on sandstorm:
https://apps.sandstorm.io/app/a0n6hwm32zjsrzes8gnjg734dh6jwt7x83xdgytspe761pe2asw0
In the case of evaluating a function like
"=GoogleFinance("usdeur","price",date(2013,12,1),date(2013,12,16))"
This can be done this without evaluate by directly referring to other cells like this:
=GoogleFinance(A10,"price",E3,E6)
Simple hack to evaluate formulas in google spreadsheet:
select cells or columns with formulas
go Edit -> Find and replace...
check "Also search in formulas"
replace "=" to "=="
replace back "==" to "="
in the same "Find and replace" window uncheck "Also search in formulas"
formulas will evaluate! :)
Thank you for user3626588's workaround here and it does indeed work. Based off your instructions it looks like it can be simplified even further.
In Cell B1 Enter the following:="=sum(A1:A5)"
In Cell C1 Set a data validation and select B1 with dropdown option.
Now select C1 and select the formula from the dropdown, it will sum any values between A1 through A5 automatically.
I have a sheet where I was creating a complicated formula for multiple values and this process worked!
Thank you once again as I was trying to avoid a script since I have data that is being pulled by another program on my worksheet. Script function do not always run automatically in those situations.
Here is the trick. Insert formula in the required cell, then get retrieve that cell value and replace the already inserted formula with this new value.
function calculateFormula(row, col){
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("Sheet Name");
sheet.getRange(row,col).setValue("=sum(D6,C12:C14)");
sheetData = sheet.getDataRange().getValues();
var newValue = sheetData[row-1][col-1];
sheet.getRange(row,col).setValue(newValue);
}
How about just converting a column of expressions which are not preceded by a "+"?
92/120
67/85
etc.
It's a bit of a hack, but this works
get the formula from the cell;
set the formula back again; then
get the value from the cell.
var cell = sheet.getRange("A1");
var formula = cell.getFormula();
cell.setFormula(formula);
var fileCell = cell.getValue();
Awesome work around for google not having evaluate(). I have looked all around and besides script have found no other way to have a formula as a string on one sheet then use that formula on another. In fact everything I've seen says you can't. Would be helpfull if anyone reading this could repost around if they come to an appropriate question since I must have read a half dozen posts saying it wasn't possible before I just rolled up my sleaves and done done it. :) It still has a little clunkyness since you need two cells in the spreadsheet you want the formula to execute, but here goes.
Ok, some set up. We'll call the spreadsheet with the formula as string SpreadsheetA, call the tab the formula is on TabAA, the Spreadsheet you want to call and execute said formula SpreadsheetB. I'll use a multi-tab example, so say you want the sum of A1:A5 on SpreadsheetB tab: TabBA to be calculated on SpreadsheetB tab: TabBB cell A1. Also call the URL of spreadsheet A: URLA
So, in Spreadsheet A Tab: TabAA cell A1 put ="=sum(TabBB!A1:A5)", therefore the cell will display: =sum(A1:A5). Note: you don't need any $ in formula. Then in Spreadsheet B, Tab: TabBB, cell A2 put: =Query(Importrange("URLA","TabAA!A1"),"select Col1 where Col1 <> ''"). That cell will now display =sum(TabBA!A1:A5). Next to that, cell A1 of Spreadsheet B tab: TabBB, create a dropdown of the cell with the formula in B2 (right click cell A1, select data validation, for Criteria select: List from range, enter B2 in box to right). That cell should now be summing SpreadsheetB, TabBA, range A1:A5
Hope that was clear, I'm rather novice at this. Also important, obviously you would only do this in cases where you wanted to choose from multiple formulas on spreadsheetA, instead of TabAA!A1 say you had another formula in A2 also so your query would be =Query(Importrange("URLA","TabAA!A1:A2"). I understand in the simplistic case given you would simply put the formula where you needed the sum.
Edit: Something I noticed, was when I wanted to use a formula with double quotes the above scenario didn't work because when you wrapped the formula with double quotes in double quotes you get an error since you need single quotes inside double quotes. The example I was trying: if(counta(iferror(query(B15:C,"select C where C = 'Rapid Shot' and B = true")))>0,Core!$C$18+$C$10&" / ",)&Core!$C$18+$C$10&if(Core!$C$18>5," / "&Core!$C$18-5+$C$10,)&if(Core!$C$18>10," / "&Core!$C$18-10+$C$10,)&if(Core!$C$18>15," / "&Core!$C$18-15+$C$10,)
In that case I put another formula into Spreadsheet A TabAA cell A2 that read ="="&A1. Then, ajusted the importrange referance in spreadsheet B to reference that cell instead.
BTW, this absolutly works so if you can't get it let me know where your having problems, I don't do a lot of colaboration so maybe I'm not saying something clear or using the right / best terminollagy but again I've seen many posts saying this was impossible and no one saying they had found another way.
Thanx ~ K to the D zizzle.
Here is the working trick to evaluate the concatenated formula string. Use the formula cell as a data validation source for the target cell. Maybe it is not a fully automated solution. But evaluating refreshed formulas has been stripped down to just one click. You just need to reselect the value from the validation box when it is necessary. Many thanks to #Aurielle Perlmann and #user3626588 for the idea.
As an example, when you have set up dynamic multiple concatenations of such below formula in another sheet, this will work well with selecting validation option.
In my case, pressing enter twice is not userfriendly.
=({FILTER(IMPORTRANGE("https://docs.google.com/spreadsheets/d/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/edit"; "EXPENSES!A2:P"); INDEX(IMPORTRANGE("https://docs.google.com/spreadsheets/d/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/edit"; "EXPENSES!A2:P"); 0; 1) <> ""); FILTER(IMPORTRANGE("https://docs.google.com/spreadsheets/d/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/edit"; "EXPENSES!A2:P"); INDEX(IMPORTRANGE("https://docs.google.com/spreadsheets/d/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/edit"; "EXPENSES!A2:P"); 0; 1) <> ""); FILTER(IMPORTRANGE("https://docs.google.com/spreadsheets/d/cccccccccccccccccccccccccccccccccccccccccc/edit"; "EXPENSES!A2:P"); INDEX(IMPORTRANGE("https://docs.google.com/spreadsheets/d/cccccccccccccccccccccccccccccccccccccccccc/edit"; "EXPENSES!A2:P"); 0; 1) <> "")})
[enter image description here]
[enter image description here]

Is there a way to evaluate a formula that is stored in a cell?

In a Google Docs spreadsheet, I'm looking for something like =EVAL(A1) where A1 is set to "=1+2".
I found out that in MS Excel there is an EVALUATE() function (which seems a bit tricky to use properly). But I could not find anything similar in Google Docs.
I also searched through the function list, but could not find anything helpful...
No, there's no equivalent to Excel's EVALUATE() in Google Sheets.
There's long history behind this one, see this old post for instance.
If you're just interested in simple math (as shown in your question), that can be done easily with a custom function.
function doMath( formula ) {
// Strip leading "=" if there
if (formula.charAt(0) === '=') formula = formula.substring(1);
return eval(formula)
}
For example, with your A1, put =doMath(A1) in another cell, and it will be 3.
I know this an old post. I'm just wondering, why nobody suggested:
myCell.getValue();
This will give you the result of the formula in myCell (3 in your example).
If you want to write the result to the cell (instead of the formula), you could use:
function fixFormula(myCell) {
myCell.setValue(myCell.getValue());
}
Short answer
As was mentioned previously, Google Sheets doesn't have a built-in EVALUATE function, but Google Sheets could be extended to add this function. Fortunately some SocialCalc files could be used to make this easier.
Script
On Google spreadsheet I'm sharing my progress. At this time I added the SocialCalc files that I think that are required and a couple of functions, and several test cases.
NOTES:
Google Sheets specific functions like FILTER, UNIQUE, among others are not available in SocialCalc as well as other functions like SIGN.
I think that the SocialCalc file should be replaced by those on https://github.com/marcelklehr/socialcalc as it looks to be updated recently. H/T to eddyparkinson (see https://stackoverflow.com/a/16329364/1595451)
Uses
The EVALUATE function on the linked file could be used as a custom function.
Example 1
A1: '=1+2 (please note the use of an apostrophe to make the formula be treated by Google Sheets as a string.
B1 formula:
=EVALUATE(A1)
B1 display value:
3
Example 2
To "EVALUATE" a formula like =VLOOKUP(2,A1:B3,2), at this time we need to use the "advanced" parameters. See the following example:
B1: '=VLOOKUP(2,A1:B3,2)
C1 formula:
=EVALUATE(B1,"data","A1:B3")
C1 display value:
B
Code.gs
/**
*
* Evaluates a string formula
*
* #param {"=1+1"} formula Formula string
* #param {"Tests"} sheetName Target sheet.
* #param {"A1"} coord Target cell.
*
* #customfunction
*
*/
function EVALUATE(formula,sheetName,coord){
// SocialCalc Sheet object
var scSheet = new SocialCalc.Sheet();
if(sheetName && coord){
// Pass values from a Google sheet to a SocialCalc sheet
GS_TO_SC(scSheet,coord,sheetName);
}
var parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(formula.substring(1));
var value = SocialCalc.Formula.evaluate_parsed_formula(parseinfo,scSheet,1); // parse formula, allowing range return
if(value.type != 'e'){
return value.value;
} else {
return value.error;
}
}
/**
*
* Pass the Google spreadsheet values of the specified range
* to a SocialCalc sheet
*
* See Cell Class on socialcalc-3 for details
*
*/
function GS_TO_SC(scSheet,coord,sheetName){
var ss = SpreadsheetApp.getActiveSpreadsheet();
if(sheetName){
var sheet = ss.getSheetByName(sheetName);
var range = sheet.getRange(coord);
} else {
var range = ss.getRange(coord);
}
var rows = range.getNumRows();
var columns = range.getNumColumns();
var cell,A1Notation,dtype,value,vtype;
// Double loop to pass cells in range to SocialCalc sheet
for(var row = 1; row <= rows; row++){
for(var column = 1; column <= columns; column++){
cell = range.getCell(row,column);
A1Notation = cell.getA1Notation();
value = cell.getValue();
if(cell.isBlank()){
dtype = 'b';
vtype = 'b';
} else {
switch(typeof value){
case 'string':
dtype = 't';
vtype = 't';
break;
case 'date':
case 'number':
dtype = 'v'
vtype = 'n';
break;
}
}
scSheet.cells[A1Notation] = {
datavalue: value,
datatype: dtype,
valuetype: vtype
}
}
}
}
formula1.gs
https://github.com/DanBricklin/socialcalc/blob/master/formula1.js
socialcalcconstants.gs
https://github.com/DanBricklin/socialcalc/blob/master/socialcalcconstants.js
socialcalc-3.gs
https://github.com/DanBricklin/socialcalc/blob/master/socialcalc-3.js
If you want to evaluate simple math(like A1: "(1+2)*9/3"), you can use query:
=query(,"Select "&A1&" label "&A1&" ''",0)
Basic math sent to query's select is evaluated by query.
Copy and paste the formulas:
Maybe you can copy and paste the formulas you need from "jQuery.sheet". Moved to:
https://github.com/Spreadsheets/WickedGrid
Looks to be all "open source"
Wont fix the issue
Also: The issue "Enable scripts to use standard spreadsheet functions" is marked as "Wont fix", see https://code.google.com/p/google-apps-script-issues/issues/detail?id=26
Ethercalc
there is a google like opensource spreadsheet called Ethercalc
GUI Code:
https://github.com/audreyt/ethercalc
Formulas: https://github.com/marcelklehr/socialcalc
Demo - on sandstorm:
https://apps.sandstorm.io/app/a0n6hwm32zjsrzes8gnjg734dh6jwt7x83xdgytspe761pe2asw0
In the case of evaluating a function like
"=GoogleFinance("usdeur","price",date(2013,12,1),date(2013,12,16))"
This can be done this without evaluate by directly referring to other cells like this:
=GoogleFinance(A10,"price",E3,E6)
Simple hack to evaluate formulas in google spreadsheet:
select cells or columns with formulas
go Edit -> Find and replace...
check "Also search in formulas"
replace "=" to "=="
replace back "==" to "="
in the same "Find and replace" window uncheck "Also search in formulas"
formulas will evaluate! :)
Thank you for user3626588's workaround here and it does indeed work. Based off your instructions it looks like it can be simplified even further.
In Cell B1 Enter the following:="=sum(A1:A5)"
In Cell C1 Set a data validation and select B1 with dropdown option.
Now select C1 and select the formula from the dropdown, it will sum any values between A1 through A5 automatically.
I have a sheet where I was creating a complicated formula for multiple values and this process worked!
Thank you once again as I was trying to avoid a script since I have data that is being pulled by another program on my worksheet. Script function do not always run automatically in those situations.
Here is the trick. Insert formula in the required cell, then get retrieve that cell value and replace the already inserted formula with this new value.
function calculateFormula(row, col){
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName("Sheet Name");
sheet.getRange(row,col).setValue("=sum(D6,C12:C14)");
sheetData = sheet.getDataRange().getValues();
var newValue = sheetData[row-1][col-1];
sheet.getRange(row,col).setValue(newValue);
}
How about just converting a column of expressions which are not preceded by a "+"?
92/120
67/85
etc.
It's a bit of a hack, but this works
get the formula from the cell;
set the formula back again; then
get the value from the cell.
var cell = sheet.getRange("A1");
var formula = cell.getFormula();
cell.setFormula(formula);
var fileCell = cell.getValue();
Awesome work around for google not having evaluate(). I have looked all around and besides script have found no other way to have a formula as a string on one sheet then use that formula on another. In fact everything I've seen says you can't. Would be helpfull if anyone reading this could repost around if they come to an appropriate question since I must have read a half dozen posts saying it wasn't possible before I just rolled up my sleaves and done done it. :) It still has a little clunkyness since you need two cells in the spreadsheet you want the formula to execute, but here goes.
Ok, some set up. We'll call the spreadsheet with the formula as string SpreadsheetA, call the tab the formula is on TabAA, the Spreadsheet you want to call and execute said formula SpreadsheetB. I'll use a multi-tab example, so say you want the sum of A1:A5 on SpreadsheetB tab: TabBA to be calculated on SpreadsheetB tab: TabBB cell A1. Also call the URL of spreadsheet A: URLA
So, in Spreadsheet A Tab: TabAA cell A1 put ="=sum(TabBB!A1:A5)", therefore the cell will display: =sum(A1:A5). Note: you don't need any $ in formula. Then in Spreadsheet B, Tab: TabBB, cell A2 put: =Query(Importrange("URLA","TabAA!A1"),"select Col1 where Col1 <> ''"). That cell will now display =sum(TabBA!A1:A5). Next to that, cell A1 of Spreadsheet B tab: TabBB, create a dropdown of the cell with the formula in B2 (right click cell A1, select data validation, for Criteria select: List from range, enter B2 in box to right). That cell should now be summing SpreadsheetB, TabBA, range A1:A5
Hope that was clear, I'm rather novice at this. Also important, obviously you would only do this in cases where you wanted to choose from multiple formulas on spreadsheetA, instead of TabAA!A1 say you had another formula in A2 also so your query would be =Query(Importrange("URLA","TabAA!A1:A2"). I understand in the simplistic case given you would simply put the formula where you needed the sum.
Edit: Something I noticed, was when I wanted to use a formula with double quotes the above scenario didn't work because when you wrapped the formula with double quotes in double quotes you get an error since you need single quotes inside double quotes. The example I was trying: if(counta(iferror(query(B15:C,"select C where C = 'Rapid Shot' and B = true")))>0,Core!$C$18+$C$10&" / ",)&Core!$C$18+$C$10&if(Core!$C$18>5," / "&Core!$C$18-5+$C$10,)&if(Core!$C$18>10," / "&Core!$C$18-10+$C$10,)&if(Core!$C$18>15," / "&Core!$C$18-15+$C$10,)
In that case I put another formula into Spreadsheet A TabAA cell A2 that read ="="&A1. Then, ajusted the importrange referance in spreadsheet B to reference that cell instead.
BTW, this absolutly works so if you can't get it let me know where your having problems, I don't do a lot of colaboration so maybe I'm not saying something clear or using the right / best terminollagy but again I've seen many posts saying this was impossible and no one saying they had found another way.
Thanx ~ K to the D zizzle.
Here is the working trick to evaluate the concatenated formula string. Use the formula cell as a data validation source for the target cell. Maybe it is not a fully automated solution. But evaluating refreshed formulas has been stripped down to just one click. You just need to reselect the value from the validation box when it is necessary. Many thanks to #Aurielle Perlmann and #user3626588 for the idea.
As an example, when you have set up dynamic multiple concatenations of such below formula in another sheet, this will work well with selecting validation option.
In my case, pressing enter twice is not userfriendly.
=({FILTER(IMPORTRANGE("https://docs.google.com/spreadsheets/d/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/edit"; "EXPENSES!A2:P"); INDEX(IMPORTRANGE("https://docs.google.com/spreadsheets/d/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/edit"; "EXPENSES!A2:P"); 0; 1) <> ""); FILTER(IMPORTRANGE("https://docs.google.com/spreadsheets/d/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/edit"; "EXPENSES!A2:P"); INDEX(IMPORTRANGE("https://docs.google.com/spreadsheets/d/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/edit"; "EXPENSES!A2:P"); 0; 1) <> ""); FILTER(IMPORTRANGE("https://docs.google.com/spreadsheets/d/cccccccccccccccccccccccccccccccccccccccccc/edit"; "EXPENSES!A2:P"); INDEX(IMPORTRANGE("https://docs.google.com/spreadsheets/d/cccccccccccccccccccccccccccccccccccccccccc/edit"; "EXPENSES!A2:P"); 0; 1) <> "")})
[enter image description here]
[enter image description here]

Google Apps Script - Spreadsheet run script onEdit?

I have a script that I use in my spreadsheet. It is at cell B2 and it takes an argument like so =myfunction(A2:A12). Internally the function gets info from a large range of cells.
Nothing seems to work. I tried adding it to
Scripts > Resources > Current Project Triggers > On edit
Scripts > Resources > Current Project Triggers > On open
How can I have this function update the result with every document edit?
When you are making calls to Google Apps services inside your custom function (like getRange and getValues etc), unfortunately there is no way of updating such custom functions with each edit, other than passing all of the cells that you are "watching" for editing.
And, perhaps even more frustratingly, the workaround of passing say a single cell that references all of your "watched" cells with a formula doesn't trigger an update - it seems that one needs to reference the "watched" cells directly.
You could pass GoogleClock() as an argument which will at least update the function output every minute.
But the advice from many members on this forum (who have much more knowledge about this stuff than me) would simply be: "don't use custom functions".
I am not sure if this exact code will work but you can try something like this...
function onEdit(event){
var activeSheet = event.source.getActiveSheet();
if(activeSheet.getName() == "ActiveSheetName") {
var targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("TargetSheetName");
targetSheet.getRange(2,2).setValue("=myfunction(A2:A12)");
}
}
Assuming that B2 cell in on the sheet "TargetSheetName" and assuming that the edited cell is on the sheet "ActiveSheetName", the function onEdit will trigger when you edited any cell in any sheet. Since there is an if statement to check if that edited cell is on the sheet "ActiveSheetName" it will trigger only if the edited cell is on that sheet and it will set the B" cell to the value =myfunction(A2:A12), forcing it to update (i guess).
hope that i am correct and that i was helpful
I had a similar issue, for me I wanted to "watch" one particular cell to trigger my function.
I did the following (pretending A1 is the cell i am watching)
IF(LEN(A1) < 1, " ", customFunction() )
This successfully triggered if I ever edited that cell. However:
"Custom functions return values, but they cannot set values outside
the cells they are in. In most circumstances, a custom function in
cell A1 cannot modify cell A5. However, if a custom function returns a
double array, the results overflow the cell containing the function
and fill the cells below and to the right of the cell containing the
custom function. You can test this with a custom function containing
return [[1,2],[3,4]];."
from: https://developers.google.com/apps-script/execution_custom_functions
which makes it almost useless, but it might work for your case?
If the custom function is assigned to a project trigger it has more power so personally I ended adding it to "Scripts > Resources > Current Project Triggers > On edit"
and basically "watched a column" so it only did things if the current cell was within the "edit range". This is a bit of a bodge and requires some hidden cells, but it works for me at the moment
var rowIndex = thisspreadsheet.getActiveCell().getRowIndex();
var colIndex = thisspreadsheet.getActiveCell().getColumn();
//clamp to custom range
if(rowIndex < 3 && colIndex != 5)
{
return 0;
}
//check against copy
var name = SpreadsheetApp.getActiveSheet().getRange(rowIndex, 5).getValue();
var copy = SpreadsheetApp.getActiveSheet().getRange(rowIndex, 6).getValue();
if(name != copy )
{
///value has been changed, do stuff
SpreadsheetApp.getActiveSheet().getRange(rowIndex, 6).setValue(name);
}