I'm trying to build an automated list with google sheets. The first sheet(A) is for input of production data of a week. The second sheet(B) should be the data archive. Thus i want the content from sheet A copied to sheet B and then deleted in sheet a. It should be copied in the next empty range in sheet B.
My problem must be inside the notation of the "while" or / and the "if" but nothing seems to work properly.
The while checks if sheet A is already emptied, if not the "if" function checks a specific range in sheet B if it is empty. If thats the case it should be copying the data and then delete it. Else the column of the range in sheet b is changed to the next range (spaltennummer + 6).
While troubleshooting it either stays in the while (finds no empty range?) or it runs through without any effect. I tried "== 0", "== """, isblank and so on. (every option available?). Google didnt seem able to provide me an answer...
Thanks for ur help.
Code:
function myFunction() {}
function leerzelle(){
var spaltennummer = 4;
var rangeDatenarchiv = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tabellenblatt1").getRange(8,spaltennummer,15,5);
var rangeDateneingabe = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dateneingabe").getRange("J8:N22");
var values = rangeDateneingabe.getValues();
while(rangeDateneingabe !== 0){
if(rangeDatenarchiv == 0) {
rangeDatenarchiv.setValues(values);
rangeDateneingabe.setValues("");
}
else{
spaltennummer = spaltennummer + 6;
}
}
}
Try this:
Code:
function leerzelle() {
var spaltennummer = 4;
// Data entry
var rangeDateneingabe = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Dateneingabe").getRange("J8:N22");
var values = rangeDateneingabe.getValues();
// While we have value in data entry sheet
while (!rangeDateneingabe.isBlank()) {
// Update archive everytime you increment in else
var rangeDatenarchiv = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Tabellenblatt1").getRange(8, spaltennummer, 15, 5);
// If archive range is blank, move values
if (rangeDatenarchiv.isBlank()) {
rangeDatenarchiv.setValues(values);
rangeDateneingabe.clearContent();
}
else {
spaltennummer = spaltennummer + 6;
}
}
}
Note:
You just can't compare a range to a number, use isBlank to check if it doesn't have values instead.
You can't use setValues("") on a range to remove the contents, use clearContent instead to delete those values.
You need to redeclare archive range everytime you loop using the incremented column number
References:
isBlank
clearContent
Related
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)
I have searched high and low but I have been unable to find an answer (I am sure I am not explaining it right)
I have a Google Sheet that have multiple sheets (tabs) labeled TabA, TabB and TabC.
On this Google Sheet, I submit a slash command on Slack, which then auto-fills a row on one of the tabs using apps script.
What I am trying to do is simply insert a word called TabA into a specific cell each time a new row has been detected. And insert a word called TabB when a new row has been made on TabB sheet etc.
I am sure I just am typing my questions wrong which is why I am unable to find an answer.
I am not actually sure which part of the code posts to the sheet, I think it is this?
if(sheetName) {
sheetName = sheetName.charAt(0).toUpperCase() + sheetName.slice(1)
} else {
sheetName = "Current"
}
// Find sheet
var sheetFlag = false;
for (var r = 1; r < settings.length; r++) {
if (settings[r][1] == channelID) {
var sheetID = settings[r][2];
var targetChannelID = settings[r][4];
var title = settings[r][0];
sheetFlag = true;
break;
}
}
if (sheetFlag) {
var sheet = SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);
if(!sheet) {
sheet = SpreadsheetApp.openById(sheetID).insertSheet(sheetName);
}
var lastRow = sheet.getLastRow();
var slackDetails = ["", "", text1, "","","","","",realName, new Date(),title,text2];
// paste the slack details to the sheet
sheet.getRange(lastRow + 1,1,1,slackDetails.length).setValues([slackDetails]);```
Thank you in advance
If I understood you correctly, you want to:
Keep track of new rows that are added to each sheet in your spreadsheet (TabA, TabB, TabC).
Write the name of the sheet in successive rows of column D of each sheet every time news rows are detected.
As you were told in the comments, Apps Script has no triggers to track changes made to the spreadsheet by a script. For example, onEdit trigger "runs automatically when a user changes the value of any cell in a spreadsheet".
Workaround (time-based trigger and script properties):
A possible workaround to this limitation is using a time-based trigger that will fire a function periodically. You can create this trigger manually, or programmatically, by running this function once:
function createTrigger() {
ScriptApp.newTrigger("trackRows")
.timeBased()
.everyMinutes(1)
.create();
}
This will fire the function trackRows every minute. This function's purpose is to track changes to each sheet rows since last time it was fired (in this example, 1 minute ago) and write the sheet name to a certain cell if the sheet has more rows with content than during last execution.
To accomplish this, you can use the Properties Service, which lets you store key-value pairs and use them in later executions.
The function trackRows could be something along the following lines:
function trackRows() {
var props = PropertiesService.getScriptProperties();
var ss = SpreadsheetApp.openById("your-spreadsheet-id"); // Please change accordingly
var sheets = ss.getSheets();
sheets.forEach(function(sheet) {
var sheetName = sheet.getName();
var currentRows = sheet.getLastRow();
var oldRows = props.getProperty(sheetName);
if (currentRows > oldRows) {
var firstRow = 2;
var column = 4;
var numRows = sheet.getLastRow() - firstRow + 1;
var rowIndex = sheet.getRange(firstRow, column, numRows).getValues().filter(function(value) {
return value[0] !== "";
}).length;
var cell = sheet.getRange(rowIndex + firstRow, column);
cell.setValue(sheetName);
}
props.setProperty(sheetName, currentRows);
});
}
This function does the following:
Retrieve the script properties that were stored in previous executions.
Get all the sheets in the spreadsheet.
Check the last row with content in each sheet (via Sheet.getLastRow()), and compare the value with the one previously stored in script properties.
If the current last row is higher than the one stored in properties, write the sheet name in the first empty row of column D of the corresponding (starting at D2).
Store the current last row in script properties.
Notes:
The script is adding the sheet name to the first empty row of column D once, if it detects that new rows were added. It's not taking into account how many rows were added since last execution, it only considers if rows were added. This could easily be changed though, if that's what you wanted.
If you want to start from fresh, it would be useful to delete all previously stored properties. To do that, you could run this function once:
function deleteProps() {
var props = PropertiesService.getScriptProperties();
props.deleteAllProperties();
}
Reference:
Class ClockTriggerBuilder
Class PropertiesService
Sheet.getLastRow()
I'll start this off by saying I have no clue what I'm doing. I'm surviving off copying and pasting code off the internet for a spreadsheet me and my friends use for watching films together.
I've run into an issue where I'm updating a cell with the current date when another cell in that row is updated if its blank with a script.
This issue is I then use a function in the cell next to it to give the difference in days for another date marked down in a cell (like a normal spreadsheet as that easier for me to do). But every time the script runs the function breaks and is replaced with the text "#NUM!" (Actually has that text as the function disappears from inside it).
I tried changing it to =U2 and that breaks also. Is this something that can't be done? The great almighty google god has not provided me with an answer so I've made an account here in hope of salvation.
tl;dr Scrips look like they are breaking my cell references for any sheet function that looks at cells they edit. How stop?
In cell V2 I have the function =DATEDIF(S2,U2,"D")
Script bellow (I know not how to format)
function onEdit(event) {
var eventRange = event.range;
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();
if (sheetName == "Scores") {
if (eventRange.getColumn() == 10) { //Check which is updated
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);//where to write
var values = columnXRange.getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) { // If cell isn't empty
values[i][0] = new Date();
}
}
columnXRange.setValues(values);
}
}
}
Ok, I see the problem. You are looking at a way bigger range than you want with
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, eventRange.getNumRows(), 21);
You only really need the value of one cell to check if it is empty. Try replacing your function with :
function onEdit(event) {
var eventRange = event.range;//makes shit happen?
var sheetName = SpreadsheetApp.getActiveSheet().getSheetName();//checks current shit
if (sheetName == "Scores") {//name of sheet want shit to happen
if (eventRange.getColumn() == 10) { // 1 is column A, 2 is B ect
// getRange(row, column, numRows, numColumns) sheet name to make not everywhere
var columnXRange = SpreadsheetApp.getActive().getSheetByName("Scores").getRange(eventRange.getRow(), 21, 1, 1);//num is where will write 1 is a ect
var values = columnXRange.getValues();//takes all shit from above to use as range
if (!values[0][0]) { // If cell isn't empty
values[0][0] = new Date();//set date to the vaules in the range
}
columnXRange.setValues(values); //use the values set above and write them in
}
}
}
..and that should fix your problem. The problem with your current script is that the script is copying the "value" of your column v cells and replacing it with just a text value. This limits the range you are grabbing to just the cell you need, eliminates the for() loop, and steps over the problem entirely.
I am stuck with, at first sight, simple script.
I want to clear a content from cell S when T has value "Copied".
What I have at the moment is this:
function onEdit(e) {
if(e.range.columnStart === 20) {
e.range.offset(0,-1).clearContent();
}
}
I am not sure how to include IF. Also, bear in mind that T column has a formula, so I don't edit it manually, and with this script, it doesn't work.
It doesn't have to be OnEdit, I can set a trigger to run the script every minute which is even better, but it is important to filter it by the value Copied.
To explain a bit more how my file works (example):
1) I add a comment in the cell S5.
2) My second script runs every minute where it copies values from column S to column V.
3) In the column T, I have the formula (=IF(V5<>"",IF(RegExMatch(S5,V5),"Copied",""),"")), which means if the value exist in the column V5 add Copied in cell T5.
4) I am looking for a solution that when cell T:T has "Copied", delete the cell range S:S
Thank you millions!
As #TheWizEd points out the value in T is dependant on the result in another cell. However an OnEdit function does not necessarily have to respond to the range where the change was made. I've used this code to use the OnEdit event to evaluate the values in Column T and then make the relevant change to values in Column S.
Column T uses a for loop to go through the various row, but the relevant value is pushed to array. This allows a single setValues to be executed at the end of the function.
The function should be assigned to the OnEdit trigger for the Spreadsheet.
function so_53469142() {
// Setup spreadsheet and target sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("so_53469142");
// get the last row of data
var lastRow = sheet.getLastRow();
// get range for Column S & T
var values = sheet.getRange(1, 19, lastRow, 2).getValues();
// set counter variable
var i = 0;
var dataArray = [];
var masterArray = [];
// start loop
for (i = 0; i < lastRow; i++) {
// Logger.log("i="+i+", S = "+values[i][0]+", T = "+values[i][1]);//DEBUG
// empty the array
dataArray = [];
// test value of first row in T
if (values[i][1] === "Copied") {
// If value = "Copies then push blank onto array for Column S
dataArray.push("");
} else {
// else push existing value for column S
dataArray.push(values[i][0]);
}
// make the array 2D
masterArray.push(dataArray);
}
// Update values in Column S
sheet.getRange(1, 19, lastRow).setValues(masterArray);
}
I have a custom function that looks up the current number of likes for a specific facebook page. I also have a custom function, set to run once a day by a trigger,that is supposed to copy and paste the cells containing the facebook query function. This will allow me to have in two columns 1) date and 2) number of likes, updating for each new day. I have to copy and paste values before the day is out so that the number of likes for each day does not automatically recalculate to the current number of likes.
The problem I am having is between the copying and pasting of the formula and copying and pasting of the values. It appears the formula is not loading fast enough as I keep on pasting the value "thinking...". I have tried the Utilities.Sleep() function to pause the script before copy and pasting values but it does not appear to solve the problem. Both custom functions are below. Any advice?
function FBlikes(url) {
var jsondata = UrlFetchApp.fetch("https://graph.facebook.com/"+url);
var object = Utilities.jsonParse(jsondata.getContentText());
return object.likes; //returns the number of "likes"
}
//pastes FB likes
function updateFbLikes() {
var s = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Like History");
var rangeToCopy = s.getRange("A2:E2"); //sets second line as the range to be copied
var range = s.setActiveCell("A331");
var notLastRow = true;
while(notLastRow){ //finds last row
if(range.getRow() == s.getLastRow()){
notLastRow = false;
s.insertRowAfter(s.getLastRow());
}
range = range.offset(1, 0);
}
rangeToCopy.copyTo(range); // Paste the data
range = range.offset(0, 0, 1, 5); //grab the whole new pasted row
Utilities.sleep(10000); //slowdown so data will load before pasting values
range.copyValuesToRange(s, range.getColumn(), range.getColumn()+4, range.getRow(), range.getRow()); //paste values
}
If I understand correctly and what you are trying to do is take the results from row 2 and place them at the end of the page as static values.
If so you might try:
Delete all unused rows from the bottom of the sheet
Use the following code
function updateFbLikes() {
var s = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Like History");
var valuesToCopy = s.getRange("A2:E2").getValues();
s.appendRow(valuesToCopy[0]);
}
If there is more to it please let me know.