I have a simple function to get some cell value
function getValue() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[4];
var range = sheet.getRange("C2:C4");
var cell = range.getCell(1, 1); // "C2"
if (cell.isBlank()) {
return 'error'
} else {
return cell.getValue()
}
}
But when I change data in C2, cell, which contains =getValue() function does not refresh itself instantly. Only if I run script again and get back to sheet. Is it possible to speed this process up? Any code for this? Thanks.
If you have to use the custom functions for this situation, how about this workaround? I don't know whether this is the best way for you. Please think of this as one of several answers.
The flow of script is as follows.
Flow :
Retrieve all values and formulas on the sheet.
Remove values of cells which have formulas.
Reflect values to the sheet using SpreadsheetApp.flush().
Import formulas to the removed cells.
By onEdit(), when you edit the cell, this sample script is launched.
Sample script :
function onEdit(e) {
var range = e.source.getDataRange();
var data = range.getValues();
var formulas = range.getFormulas();
var values = data.map(function(e){return e.slice()});
for (var i in formulas) {
for (var j in formulas[i]) {
if (formulas[i][j]) {
data[i][j] = formulas[i][j];
values[i][j] = "";
}
}
}
range.setValues(values);
SpreadsheetApp.flush();
range.setValues(data);
}
Note :
In this situation which imports a value at "C2" to the cell at =getValue(), the refresh speed is slower than that of #random-parts's method.
To use onEdit() is also proposed from #Cooper.
If this was not useful for you, I'm sorry.
Related
I found this script:
function onEdit () {
var s = SpreadsheetApp.getActiveSheet ();
if (s.getName () == "sheet_name") {
var r = s.getActiveCell ();
if (r.getColumn () == 1) {
var nextCell = r.offset (0, 1);
if (nextCell.getValue () === '')
nextCell.setValue (new Date());
}
}
}
It works if I fill one cell by myself and puts current date to another cell in the right.
BUT if I use Zapier to export my data from Todoist to Google Sheets this script doesn't work. It only works if I change something manually.
Is there any way to make a script which will fill a cell I need with a today date when Zapier export data and fills cells automatically?
Suggestion:
As what Tanaike mentioned, you need to rename your function to something else aside from onEdit() since onEdit is a reserved function name for App Script and use onChange trigger.
But based on how Zapier works, the reason why the current code you have provided is not working is because exports from Zapier is not detected as an active cell, so we would need to revamp the entire code.
Try this instead:
function onZapierUpdate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet2'); //my test sheet is Sheet2
var range = sheet.getRange(2,1,sheet.getLastRow()-1, 2);
var data = range.getValues();
data.forEach(x => x[0] != "" ? x[1] = new Date() : x);
range.setValues(data);
}
After saving the script, set this on an onChange trigger like so:
Now whenever Zapier exports the data, it changes the content of the spreadsheet which means onChange trigger will take effect.
Reference:
https://developers.google.com/apps-script/reference/script/spreadsheet-trigger-builder#onChange()
So I have multiple files that have a column where I would like to update in the formula. However, there might be a certain cell that already has a value in it, but I don't want to replace it with the formula (see screenshot for reference).
I read some references here, but haven't found a similar case like mine.
This is the attempt that I do, but it's not working:
function updateWithFormula(){
/*** Input Data From Multiple Sources ****/
var sourceWorkbook = SpreadsheetApp.openById('')//id of the workbook
//Open tab 'Sheet1' and pull the data inside the script
var sourceSheet = sourceWorkbook.getSheetByName('Sheet1')
var source = sourceSheet.getDataRange().getDisplayValues()
for(row in source){
if (source[row][3]=="Update Value") {
//open files through link
var files = SpreadsheetApp.openByUrl(source[row][2]) //there's a link inside this column that linked to the file that I want to update
/*******insert formula *******/
//get range that want to be inserted by the formula, which is column S
//if the column S already have value in it, I don't want to do anything in it, however if it doesn't have value, I would like to put a formula
var result = files.getSheetByName('Sheet1').getRange("S2:S") //this is the column that I want to update
//set formula
for(r in result)
{
if(result[r] == "")
result[r].setFormula("=R"+ r+1)
}
}
}
}
Do you guys have any idea why my code is not working? Any advice for this case?
Thank you!
Objective
If I understood correctly, your objectives are the following:
Retrieve data from a "master" spreadsheet with information on which spreadsheets to update.
Loop through said data and locate the spreadsheets (represented as rows) that require updating.
Open those spreadsheets individually.
Update those spreadsheets rows with a sheets formula if a certain condition is met (in this case, that the cell is blank).
Issues
The for(var a in b) syntax in javaScript is used to iterate through object, not arrays. You should change it to:
for (var i = 0; i<source.length; i++){
//YOUR CODE
}
where: source[i] lets you access that specific row.
When you try to get the individual sheets' values, you are actually only getting the range, not the values themselves. You should replace this:
var result = files.getSheetByName('Sheet1').getRange("S2:S")
with this:
var sheet = files.getSheetByName('Sheet1');
var range = sheet.getRange("S2:S");
var values = range.getValues();
(You can read more about ranges and how they work here).
To input values into a spreadsheet, you should do it by using the setValue() method in the range class. Again, go here for more info. So, instead of:
result[r].setFormula("=R"+ r+1)
use:
var rangeToModify = sheet.getRange(j, 19); //LETTER S IS THE 19TH
rangeToModify.setValue("=R"+ (j+1)); //SET THE FORMULA
Final Code
function updateWithFormula(){
var sourceWorkbook = SpreadsheetApp.openById('')//id of the workbook
//Open tab 'Sheet1' and pull the data inside the script
var sourceSheet = sourceWorkbook.getSheetByName('Sheet1')
var source = sourceSheet.getDataRange().getDisplayValues()
for(var i = 0; i<source.length; i++){
if (source[i][3]=="Update Value"){
var files = SpreadsheetApp.openByUrl(source[row][2]);
var sheet = files.getSheetByName('Sheet1');
var range = sheet.getRange("S2:S");
var values = range.getValues();
//set formula
for(var j = 0; j<values.length; j++){
if (values[j] == ""){
//GET THE RANGE THAT YOU WANT TO MODIFY
var rangeToModify = sheet.getRange(j, 19); //LETTER S IS THE 19TH
rangeToModify.setValue("=R"+ (j+1)); //SET THE FORMULA
}
}
}
}
}
I believe your current situation and your goal are as follows.
"Sheet1" of sourceWorkbook has the Spreadsheet URLs and the value of "Update Value" in the columns "C" and "D", respectively.
You want to retrieve the Spreadsheet from the URL, and want to check the column "S2:S" of of "Sheet1" in the retrieved Spreadsheet, and want to put a formula like "=R"+ r+1 to the non-empty cells of the column "S".
In this case, how about the following modification?
Modification points:
var result = files.getSheetByName('Sheet1').getRange("S2:S") returns Class Range object. This cannot be used with for(r in result). This is the reason of but it's not working. This has already been mentioned by the Oriol Castander's answer.
When setFormula is used in a loop, the process cost becomes high.
When these points are reflected in your script, it becomes as follows.
Modified script:
function updateWithFormula() {
var sourceWorkbook = SpreadsheetApp.openById(''); // Please set your Spreadsheet ID.
var sourceSheet = sourceWorkbook.getSheetByName('Sheet1');
var source = sourceSheet.getDataRange().getDisplayValues();
source.forEach(r => {
if (r[3] == "Update Value") {
var sheet = SpreadsheetApp.openByUrl(r[2]).getSheetByName("Sheet1");
var rangeList = sheet.getRange("S2:S" + sheet.getLastRow()).getDisplayValues().flatMap(([e], i) => e == "" ? [`S${i + 2}`] : []);
if (rangeList.length > 0) {
sheet.getRangeList(rangeList).setFormulaR1C1("=R[0]C[-1]");
}
}
});
}
In this modification, the formula is put as the R1C1 using the range list. By this, I thought that the process cost will be able to be reduced a little.
References:
getRangeList(a1Notations)
setFormulaR1C1(formula)
In google apps script, with onEdit trigger, how do I get the old values of all the cells if the edited range has multiple cells? In the Event object documentation https://developers.google.com/apps-script/guides/triggers/events it is specified that the oldValue attribute is "Only available if the edited range is a single cell".
In my case, I use onEdit from the Event object to run a function (that needs oldValue and newValue) only when a specific column is edited. It works fine when the user selects only one cell in my specific column, but if the user selects a few cells or the entire row for example, only data from the first selected cell is retrieved but I need to access the oldValue of my specific column.
You want to retrieve old values when the multiple cells are edited.
If my understanding is correct, how about this answer?
Issue:
Unfortunately, in the current stage, when the multiple cells are edited, there are no methods for retrieving all old values from the event object.
Workaround:
So as the current workaround, how about this flow?
Copy the active Spreadsheet.
This is run only one time.
When the cells are edited, the old values are retrieved by comparing the active Spreadsheet and copied Spreadsheet.
Update the copied Spreadsheet.
By above flow, the cycle for retrieving old values when the cells are edited can be created. When this flow is reflected to the script, it becomes as follows. Please think of this as just one of several answers.
Sample script:
When you use this script, please install the OnEdit event trigger to the function of onEditByTrigger() after copy and paste this script to the script editor of the container-bound script. By this, when the cells are edited, you can see the current and old values at the log.
var backupfilename = "backupfile";
function copyToo(srcrange, dstrange) {
var dstSS = dstrange.getSheet().getParent();
var copiedsheet = srcrange.getSheet().copyTo(dstSS);
copiedsheet.getRange(srcrange.getA1Notation()).copyTo(dstrange);
dstSS.deleteSheet(copiedsheet);
}
// This is run only one time.
function init() {
// Source
var srcss = SpreadsheetApp.getActiveSheet();
var range = srcss.getDataRange().getA1Notation();
var srcrange = srcss.getRange(range);
var srcsheetname = srcss.getName();
// Destination
var backupfile = DriveApp.getFilesByName(backupfilename);
var dstid = backupfile.hasNext()
? backupfile.next().getId()
: SpreadsheetApp.create(backupfilename).getId();
var dstss = SpreadsheetApp.openById(dstid).getSheets()[0]
var dstrange = dstss.getRange(range);
dstss.setName(srcsheetname);
copyToo(srcrange, dstrange);
PropertiesService.getScriptProperties().setProperty('backupfileid', dstid);
return dstid;
}
function onEditByTrigger(e) {
var columnNumber = 1; // If you want to retrieve the old values when the column "A" is edited, it's 1.
var source = e.source;
var range = e.range;
var dstid = PropertiesService.getScriptProperties().getProperty('backupfileid');
if (!dstid) {
dstid = init();
}
if (e.range.columnStart == columnNumber) {
var range = source.getSheetName() + "!" + range.getA1Notation();
var fields = "sheets(data(rowData(values(formattedValue,userEnteredFormat,userEnteredValue))))";
var currentValue = source.getRange(range).getValues();
var oldValue = SpreadsheetApp.openById(dstid).getRange(range).getValues();
Logger.log("currentValue %s", currentValue)
Logger.log("oldValue %s", oldValue)
}
// Update backup file
var range = e.source.getDataRange().getA1Notation();
var srcrange = e.source.getRange(range);
var dstrange = SpreadsheetApp.openById(dstid).getSheets()[0].getRange(range);
copyToo(srcrange, dstrange);
}
Note:
This is a sample script for retrieving the old values when the multiple cells were edited. So if you use this script, please modify this to your situation.
References:
Event Objects
Installable Triggers
If this was not the direction you want, I apologize.
I have this script running nicely on Google Sheets:
function recordMax() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("K16:L16");
var values = range.getValues()[0];
range.setValues([[values[0], Math.max(values[0], values[1])]]);
}
But, I have this effect here http://screencast.com/t/BOXzC0UZxFZ (short 1 min video show you the issue). Summary of the issue is that when the script runs in forces K16 to just show the current value of the formula and strips out the =sum formula I have in K16.
What do you think I need to do so that when the script runs, it will not automatically strip the K16 formula so K16 will continue updating to new values?
:)
How about the following modification?
Modification point :
In order to retrieve formulas in the cells, it uses getFormulas().
Insert the retrieved formulas.
Modified script :
In the most cases, the following modified script can be used. I think that in your case, the following modified script can be used.
function recordMax() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("K16:L16");
var formula = range.getFormulas()[0]; // Added
var values = range.getValues()[0];
range.setValues([[formula[0], Math.max(values[0], values[1])]]); // Modified
}
There are sometimes the case that it requires to insert the formula using setFormula() as follows.
function recordMax() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("K16:L16");
var formula = range.getFormulas()[0]; // Added
var values = range.getValues()[0];
sheet.getRange("K16").setFormula(formula[0]); // Added
sheet.getRange("L16").setValue(Math.max(values[0], values[1])); // Added
}
Reference :
getFormula()
getFormulas()
setFormula(formula)
setFormulas()
If I misunderstand your question, please tell me. I would like to modify.
Got here two scripts that change the font color based on line-through, both are working.
- First one can be custom triggered,
- Second is onEdit.
The onEdit has my preference, as it automates things instantly, however onEdit does not see changes when a cell's value is getting line-through yes or no.
So is it possible to let onEdit trigger for fontstylechanges?
function colorlinethrough() {
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Get spreadsheet
var sheet = ss.getSheets()[0]; // Get first Sheet
var range = sheet.getDataRange(); // Get cells
var data = range.getValues(); // Get the cell values
for (var i in data) {
var editrange = sheet.getRange(parseInt(i)+1,2);
if (editrange.getFontLine() == "line-through") {
editrange.setFontColor("#CCCCCC");
}
else {
editrange.setFontColor("#000000");
}
}
};
function onEdit(e) {
var ss = e.source; // Get spreadsheet
var range = ss.getActiveRange();
if (range.getFontLine() == "line-through") {
range.setFontColor("#CCCCCC");
}
else {
range.setFontColor("#000000");
}
};
also put this question on the g+ community https://plus.google.com/104787958270362345970/posts/GTdBoTZR3YF
Spreadsheets has an onChange event that might do the trick here. onEdit is when the data changes and onChange is when the spreadsheet UI itself changes. Give it a shot.
https://developers.google.com/apps-script/understanding_events