Read and write cell formulas from Google Apps Script custom function - google-apps-script

I am trying to write a GAS spreadsheet custom function that copies cell content to other cells. And I need to fill the target cells not only with the data of the source cell, but with its formula content (if it has any).
Now, I already know that this is basically impossible through custom functions as they always receive the result of cell calculations but not the cell formulas themselves, and they also cannot return formulas for their target cells.
On the other hand there are functions to read and write cell formulas, e.g. Range.getFormula() and Range.setFormula() which seem to make my endeavor possible. I simply have to find another way of calling them. UPDATE: Meanwhile I found that custom formulas in fact can read formulas using getFormula(), but they definitely don't have permission to write formulas into cells using setFormula().
My question is...
What would be the most elegant method to create something equivalent to a custom function that reads and writes formula content of cells? I think I could use an onEdit function that updates my target cells after each spreadsheet edit, but that would mean that I have to hard code the coordinates of the target cell range, which seems very hacky and would require code changes every time the target range is moved (e.g. when rows are inserted above it).
UPDATE: Example
An example would be a custom function that is able to read multiple ranges of cells (each range given as a distinct function parameter) and returns a joined range of cells.
=rangeJoin(A1:B10;D1:E15)
...would read the two ranges of size 2x10 and 2x15 and would fill a target range of size 2x25 with the subsequent cell contents of both ranges. The target range would start at the cell that contains rangeJoin and would spread 2 cells to the right and 25 cells down (as usual for a custom function). The custom function (or similar mechanism) should be able to copy formulas, so a cell containing =hyperlink("http://www.google.com";"Google") should appear in the target range as a hyperlink and not as a text cell with the naked word 'Google'.

Agree with "Mogsdad"
ie. this custom function works:
function myGrid() {
return [[1,2],[3,"http://www.google.com"]];
}
but, custom functions can't write formulas to a sheet. See https://developers.google.com/apps-script/execution_custom_functions#permissions
As a workaround, you could use a "Trigger", such as a time based trigger, as "Mogsdad" suggests.

Related

Using setformulas to copy both values and formulas down a column

PROBLEM: In the below spreadsheet, cells C12:C17 (green) contain text imported from another spreadsheet (The "PARENT").
https://docs.google.com/spreadsheets/d/1brm0dHkXG1vxn2NQ7wGvEoayCdGYKsY4yA-MX4Jtt1w/edit#gid=396314711
Some cells have text. Some are blank. Some contain simple math (i.e 1+1), and others relational math (i.e. A1+B1). The DATA SET in the PARENT sheet are different than the CHILD sheet. I will eventually have to create a lot of CHILD sheets, each with their own unique DATA SET. The formulas on the PARENT sheet will change from time to time, so the solution isn't to just make a copy of the PARENT spreadsheet and turn it into a CHILD sheet.
I need to be able to create the formulas in the PARENT sheet, but when imported into the CHILD sheet, they need to be calculated using the data set on the CHILD sheet.
I'm trying to use a script that will take what is in the C12:C17 range, and make it an active formula in the corresponding D12:D17 cells. If the C column cell is a value, it should just put that value in there instead of making it into a formula.
I've made about a dozen attempts at the setFormulas script. All failed. At this point, I would say I'm "spit-balling", "flailing", and possibly "spiraling". The BEST I've been able to do is to get ONE cell to update (but only if a formula, not text).
I'm looking for a script that will take whatever is in cells C12:C17, and execute/evaluate them into cells D12:D17. If there are text/values in the C cell, then it should put that text/value in the corresponding D cell. If there is a formula in the C cell, it should make it execute in the corresponding D cell. There is no pattern on whether a cell will be a number, text, or formula.
I appreciate any help you can give.
FYI: The formulas/text in the C column were made by importing from the PARENT sheet. That range of cells in the PARENT sheet was made by the following formula:
=IFERROR(FORMULATEXT('P CUSTOMERS'!B12),'P CUSTOMERS'!B12)
Essentially, "If it's a formula, convert it into text. If it's not a formula , just put what is in the cell in the first place ."
I've been working on this for an embarrassing amount of time... (Not hours, not days, not weeks, but MONTHS!).
EDIT / UPDATE:
OK, Marti's scripts worked great in the example file. Moved it to another file, same conditions, and worked again.
THEN, I moved it to another file... Only difference I can tell is that it is MUCH larger.
EDIT SUMMARY:
Ran the script under these conditions.
Made table with formulas. Used FORMULATEXT on that table to convert into text. Imported that range to another sheet. Used an HLOOKUP formula to select which column I wanted to look at formulas for.
In the sample sheet (linked here: https://docs.google.com/spreadsheets/d/1cPSJMXNiKDnHCiUCGJ0iSjaoIiRLQldr1T5rClUUEm0/copy), it works.
But when I run the exact same series of events in another sheet, it fails to do anything. Process is the same, only the range is different. No other scripts on this sheet.
I trimmed down the second sheet so I can share (script still doesn't work).
https://docs.google.com/spreadsheets/d/1RRMy4RtF9CVSXw18bWg79Dh4nwHg8IN3wwhLb3QynvA/edit#gid=236899042
(Note: I can't force a copy, as this sheet requires authorizations from another file)
Here is a video better explaining the issue:
https://drive.google.com/file/d/1pjz_LilRReQlNt7p_4NhU3prtbcAhLah/view?usp=sharing
So, I'm trying to understand why it works in one, but not the other... And just as important, what can I do to make it work in 2nd sheet, which is actual goal.
You can simply use getValues together with setValues. setValues actually interprets values starting with = as formulas, and getValues actually doesn't add the ' before them. So you can simply chain them:
function computeValues() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const s = ss.getSheets()[0]
const src = s.getRange('C12:C')
const target = src.offset(0, 1)
target.setValues(src.getValues())
}
References
Range.getValues() (Apps Script reference)
Range.setValues(values) (Apps Script reference)
Range.offset(rowOffset, columnOffset) (Apps Script reference)
Martí nailed it...
function computeValues() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const s = ss.getSheets()[0]
const src = s.getRange('C12:C')
const target = src.offset(0, 1)
target.setValues(src.getValues())
}
I tested it, as written, by copy/paste. Worked 1st time.
I added in more formulas below the original range, just to see what the limits were (in cell C20), and re-ran it. Still worked.
I would have kept going in my setFormula direction for another few months. Not sure how I got it stuck in my head that was the only solution. This was the final major barrier in my project, and while I still have another few years of manual data entry and formula creation to do, THIS will be the key that makes it all work. THANK YOU!!!

Write in the row under the function IMPORTRANGE

I want to write in the row under the function IMPORTRANGE but when I do that the function stop working bc it cant increase. Can someone know how to solve that pls?
here is my google sheet :
exemple: When i want to write in the row 15 column A,B,C or D the function stop working and I have a REF ERROR but I want to be able to write
The IMPORTRANGE() function occupies a range based on the data that queries. If you add some content within the datarange that importrange returns, the latter will break because it can't expand.
You can either restrict the range that importrange occupies:
=IMPORTRANGE("SprdID";"All Months!$A$1:$D14")
or add content starting from column E. You can also put the importrange function in bottom rows and use the top rows for manually entered data.
Since the raw data could potentially increase in the future, I would advice you to have a dedicated sheet to accommodate the importrange function and all of the other calculations/formulas to be stored in a different sheet.
References:
https://support.google.com/docs/thread/26662291?hl=en

How to send cell data from one google spreadsheet to another, but only if the cell colour is correct?

I have a spreadsheet with cells coloured in two different colours. I know I can send all the cell data from one google spreadsheet to another using IMPORTRANGE function. However, I only want to send the cell data if it satisfies a specified cell colour.
For example, if spreadsheet A has 10x10 data with various colours, then spreadsheet B should contain all the data from cells in spreadsheet A that are either red or green (and also transfer the cell colours). All other cells with different colours from spreadsheet A should be transferred to spreadsheet B as blank colourless cells. The resulting spreadsheet should still contain 10x10 cell data, but with only red, green and blank cells.
I know it should be possible to write a function for this, but I have never written any custom functions before and have no Javascript experience. Any kind of help would be appreciated. Perhaps also the QUERY function could be of use?
Thanks in advance!
You should check about Google Apps Script. It gives you a set of tools that will allow you to create a script for doing what you want.
Custom Functions will help you to create a function that lets you get the values from your sheet and then set the conditions you are requiring.
The Class SpreadsheetApp has the tools for handling all data in your sheets. Check for example the method getBackgrounds(), which gets the color in a range of cells.
This another post, it is a little similar in some aspects to what you want to do.
It's best practice to create an additional column which stores the information regarding as to which condition (color) is applied 5o the particular row. Once you have done that, you can easily transport a table from one Spreadsheet to another using the QUERY formula within the IMPORTRANGE.
Image column a is the name of a city.
Imagine column b holds the information regarding the condition (color). This is an helper column.
Now we have col1 = New York City, col2= green
Then you could enter this into the new sheet.
QUERY( IMPORTRANGE(URL, range), "SELECT col1 WHERE col2="green" OR col2="yellow" OR col2="red")
Here is a great tutorial series I like to use.
https://youtu.be/_N5zhAipVn0

Google spreadhseet EVAL function

I have a google spreadsheet with different sheets, each one representing a different week.
For example:
1/12 - 1/16
1/19 - 1/23
I want to do a chart based on the content of those sheets. Is there any way I can make a formula and extract the name of the sheet from a content of a cell?
For example something like "=EVAL(A1)!$B$4", then I would have the content from "1/12 - 1/16"!$B$4 instead of having to go through each one of the weeks of the year manually.
Thanks for the help!
There’s no need to use AppScript, INDIRECT is enough to read a sheet name from a cell:
=INDIRECT(A1 & "!$B$4")
However, it looks like Andy’s answer is the way to go if you want to get the sheet name from its index rather than from a cell.
It'd be best to use AppScript. In Tools -> Script Editor make a new AppScript script:
function getSheetName(i) {
var s = SpreadsheetApp.getActiveSpreadsheet().getSheets()
return s[i].getName();
}
With that in your script, you can then use the custom function =getSheetName(<SHEETNUMBER>) and it will retrieve the sheet name based what sheet number it is (starting from 0). From there, just incorporate it into your formulas. You may need to use INDIRECT.
Example: =INDIRECT(getSheetName(1)&"!A1") to get cell A1 in the second sheet.

Is it possible to define a new function in Google-docs spreadsheet?

Is it possible to define a function in Google Spreadsheets that can be used in any cell?
It would be helpful if I could define and use functions that refer to other cells in the same way that I can use native functions, e.g. by entering =myfunction(C1, C2, C3)
Yes - there's a tutorial. Just use javascript functions by name in your spreadsheet. Define them using Tools > Script editor.
Watch out for name restrictions; I was confused by the behavior of functions that I created with names like "function x10() {}" not being found. Renaming to a longer name fixed it. There are probably documented rules for what isn't allowed, but I don't know where they are.
I am a "newbee". But is is my experience that you can only access a "cell"
via the "range" object. You must define the range as a single cell.
For example "A1:A1", will give you access the the cell at "A1".
A RANGE is an object associated to a "SHEET".
A SHEET is an object associated to a "SPREADSHEET".
Here is some sample code to access cell A1 in the current active sheet:
var cell_A1 = SpreadsheetApp.getActiveSheet().getRange("A1:A1");
From here you can pass the object like any other parameter.
myFunction(cell_A1);
The receiving function must "know" that it is dealing with a "range".
It can only access its values by calling "methods" associated to the
"range" object.
Be careful! A "range" can consist of more than one cell. Your called
function should test to see that it is working with a single cell.
If you pass a range of more than one cell, your function might not
act in the way you expect.
The two methods of a range object: "getNumRows()" and "getNumColumns()"
returns the numbers of Rows and Columns in a range object.
In general, if you use methods that are limited to changing or accessing
a single cell, and operate on a larger range set, the function will only be
performed on the upper-left cell member. But be careful. While you
might assume a method will only change a single cell, it may actually
affect all cells in the range. Read the documentation closely.
There is another method to obtain a range of a single cell. Its instruction
looks like this:
var cell_B2 = SpreadsheetApp.getActiveSheet().getRange(2, 2, 1, 1).
The first two parameters tell the "getRange" function the location of the
cell (in row, column format). The second two parameters define the number of
"rows" and "columns" to associated with the range. By setting them both to
"1", you access a single cell.
Hope this helps.