Issue with variable getrange (error cannot convert to int) - google-apps-script

I am trying to do a personal project to create an additive list, which allows the user to enter a question on one sheet, press a button, and then it copies it over to the master list on another sheet.
Right now I'm having issues running the code because of
Exception: Cannot convert 'Range2' to int. copycell # copycell.gs:5
, which I believe has to do with the variable getrange. Am I setting up the variable wrong?
function copycell() {
var quest = SpreadsheetApp.getActive().getSheetByName('Main').getRange('B1') //Spot for question to be typed in
var newspot = SpreadsheetApp.getActive().getSheetByName('Questions').getRange('C1') //C1 contains count value of column
var spot = newspot + 2
var newquest = SpreadsheetApp.getActive().getSheetByName('Questions').getRange(spot,2) //cell C1+2 dictates next open row to past value to
newquest.setvalue(quest) //copies value from one sheet to the next available spot on the other sheet
}

Related

A script to Update and/or append data to another tab

Test Sheet
So I'm trying to create sort of a database to update and store data. From what I can tell, I have to more than likely create a script to run when I need to transfer that data over, but I'm having a hard to trying to figure out where to even start.
The idea is on the 'Data Collection" tab you have the names, the section they are working on, and the units they made. Once I run a script, it would transfer the names and data to the "Section Data" tab and place them in Column A then place how many units they made in the proper section cell. It would then clear the Units field in "Data Collection" so it's ready for the next day.
Here is where it gets a bit complicated. If the name already exists in the "Section Data" tab, instead of adding a new row, add the number of units to the total that is already in the cell under that name.
So in the example above, running the script would make the "Section data" tab look like "Section Data After" as an example.
Any data entered in would either increment existing data if the name already exists. If it doesn't exist, it would create a new line in the first available slot and append it to the bottom of the existing data.
As an example:
So in the first tab
Bob in Section 1 doing 11 Units
Gina in Section 2 doing 5 Units
In the second tab, I have existing data
Bob in Section 1 did 6 Units and in Section 4 did 5 Units
Jeff in Section 3 did 8 Units
If I were to hit the transfer script button on the first tab, it would move the data over to the second tab like so
Bob in Section 1 now has 17 Units and Section 4 still have 5 Units as nothing was added to it
Jeff would still have only 8 Units in section 3 as there was no new data added for him
Gina would be added under Jeff with 5 Units in Section 2
Hopefully, I explained this well enough. Thank you.
Can anyone point me in the right direction to start?
Here is a script to complete what you are looking for:
/** #OnlyCurrentDoc */
function transfer() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Data Collection');
var data = ss.getSheetByName('Section Data');
var enteredNames = data.getRange('A:A').getValues();
var sections = data.getRange('A2:2').getValues();
var start = input.getRange('A3:A7').getRowIndex();
var end = start + input.getRange('A3:A7').getNumRows();
for(var i = start; i < end; i++) {
let name = input.getRange('A'+i).getValue();
if(name != '') {
let section = Number(input.getRange('B'+i).getValue());
let units = Number(input.getRange('C'+i).getValue());
var row = enteredNames.findIndex(row => row.includes(name))+1;
var col = sections[0].indexOf(section)+1;
if(row > 0) {
var oldUnits = data.getRange(row, col).getValue();
if(oldUnits != '') {units = Number(oldUnits)+units};
data.getRange(row,col).setValue(units);
} else {
if(units == 0) {units = ''};
let inputHere = data.getLastRow()+1;
data.getRange('A'+inputHere).setValue(name);
data.getRange(inputHere, col).setValue(units);
}
}
}
input.getRange('C3:C7').clearContent();
};
This function iterates through each name in the Collection sheet, pulls the relevant and related data (section, units), and searches the Data sheet to see if that name is already in there. If the name is, it then checks to see if there is a value in the section it's looking for, and if there is it adds it. Otherwise, it just places the number. If there is no name match in the Data sheet, it adds them to the end of the list (and inputs the correct units under the section).
Please let me know if you have any issues with this.
Below is a function you can add to the same script file, or a different one. It creates a menu at the top so you can easily run the transfer function.
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Functions')
.addItem('Transfer Data', 'transfer')
.addToUi();
}
Here is the link to the sheet where I tested this. It is a direct copy of the sheet you provided, the only difference being that the script is linked to it.
https://docs.google.com/spreadsheets/d/1vGV3sypyfMviyv9k2lQt4CGunDkzYf-4TX-bp8goKz4/edit?usp=sharing
If you would like me to further explain my code, or have any other questions, please let me know.

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

getlastrow, then last column, and then populate based on that information

Recently, there was a program by the name of Eddienuput that allows you to properly train against basically anything in training mode. One issue is that you have to make a text file with all the button presses, as well as the timing for each. Once this text file is made, the inputs are added into a virtual controller.
The working sheet can be seen here.
The idea here is that you put the data of the character on the second sheet, then on the generator sheet you select the buttons you want, then press the [>] to put it to the right of the current buttons stored, and you use the [v] button.
When I hit the down arrow (I've assigned the script to the buttons), the three entries needed for the program to register the input are inserted one row below the active row.
The problem is the Right arrow. I sort of have it right (it puts the three inputs needed to the right.. but it isn't finding the bottom-right entry and putting it to the right of it, but instead it looks for the furthest right column and puts it to the right of everything instead of next to the last cell on the last row.
here is the code as I have it now:
function addbuttontoString()
{
//DEFINE ALL ACTIVE SHEETS
var ss = SpreadsheetApp.getActiveSpreadsheet();
var eddGenSheet = ss.getSheetByName("Eddie Input Generator");
var lrwithcontentofEddGenSheet = eddGenSheet.getLastRow()+1;
var lcwithcontentofEddGenSheet = eddGenSheet.getLastColumn();
var CharacterFrameDataSheet = ss.getSheetByName("CharacterFrameData");
//Current string Variables
var totalcurrentstringRows = lrwithcontentofEddGenSheet-4;
var fullcurrentString = eddGenSheet.getRange(5,1,totalcurrentstringRows, lcwithcontentofEddGenSheet);
var cslastRow = fullcurrentString.getLastRow();
var bottomrightcolumnNumber = eddGenSheet.getRange(lrwithcontentofEddGenSheet,1,1,lcwithcontentofEddGenSheet).getLastColumn();
Logger.log(bottomrightcolumnNumber)
//GET LAST ROW #OF ITEM SHEET
var lastrowItem = CharacterFrameDataSheet.getLastRow();
// Get the perfect Eddie value from Character Frame Data you are grabbing data for
var button = eddGenSheet.getRange('B4').getValue();
// Set up the Perfect Frame Variable
for(var i = 2; i <= lastrowItem; i++)
{
if(button == CharacterFrameDataSheet.getRange(i, 1).getValue())
{
var Perfectframe = CharacterFrameDataSheet.getRange(i, 4).getValue();
}
}
Logger.log(fullcurrentString.isBlank());
if(fullcurrentString.isBlank()){
// POPULATE eddGen SHEET Same Line
eddGenSheet.getRange("A5").setValue(button);
eddGenSheet.getRange("B5").setValue("F");
eddGenSheet.getRange("C5" ).setValue(Perfectframe);
} else {
eddGenSheet.getRange(lrwithcontentofEddGenSheet-1, bottomrightcolumnNumber +1).setValue(button);
eddGenSheet.getRange(lrwithcontentofEddGenSheet-1, bottomrightcolumnNumber +2).setValue("W");
eddGenSheet.getRange(lrwithcontentofEddGenSheet-1, bottomrightcolumnNumber +3).setValue(Perfectframe);
}
}
I believe your goal as follows.
When you click the right arrow button, you want to add the values from the 1st empty column of the row.
In your script, in order to retrieve the coordinate of the column for putting values, getLastColumn() is used as follows. In this case, the 1st empty column of the row is not retrieved. The last column of the sheet is retrieved. So, how about the following modification?
From:
var bottomrightcolumnNumber = eddGenSheet.getRange(lrwithcontentofEddGenSheet,1,1,lcwithcontentofEddGenSheet).getLastColumn();
To:
var bottomrightcolumnNumber = eddGenSheet.getRange(lrwithcontentofEddGenSheet - 1, 1).getNextDataCell(SpreadsheetApp.Direction.NEXT).getColumn();
In this modification, the 1st empty column is retrieved using getNextDataCell().
Note:
I'm not sure about your actual situation. So when above modification was not useful for your situation, can you provide the detail information about your actual situation? By this, I would like to modify it.
References:
getLastColumn()
getNextDataCell(direction)

Trying to limit an existing script to a specific sheet

My first issue is this, I have an items log sheet where I want to add and manage individual unique items in our inventory. I created a data validation dependent dropdown list for a main category and found out how to build a script to dynamically create a secondary category dropdown list based on the selected main category.
For Example:
If cell B2 (Main Category) is set to Carabiner (based on data validation range on another sheet) THEN cell C2 (secondary Category) will dynamically create a dropdown list relative to the Carabiner main category (i.e. locking, non-locking)
That is simple enough if you only have one row to create the dropdown lists, but I wanted to be able to pick from a secondary category list in each row dependent on which was picked in the main category cell.
I found a video of a script that did just that and got it working just fine.
Now the problem is that the script runs data validation on every other sheet. How can I limit the script to only run on a specific sheet?
Here is the script:
function onEdit() {
// this line just refers to the current file var start = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); var current = start.getActiveCell()
// var to refer to the worksheets -lists is where the data validation range will come from, and main is where we want to use that data validation range var list = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Indirect_Categ_Ranges") var main = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Items_Log");
// has the user selected a category? Refers to the column number in the Items_Log sheet where the user has picked the main category
if (current.getColumn()==2)
{
//to copy the selected sub-category -the 2,1 is the row and column on the Indirect_Categ_Ranges sheet where this script will dynamically update the main category picked to define what the indirect function will display in the next column
var choice = current.getValue()
list.getRange(2,1).setValue(choice)
//clear any validation -the 2,3,1000 looks to start clearing validation at row 2, column 3 and down for up to 1000 entries
main.getRange(2,3,5000).clearDataValidations()
// create the rule - var_point defines the offset number of rows and columns to shift to initiate the dynamic dependent dropdown list, the var_items defines where the to look for the range to build the dropdown list
var point = current.offset(0,1)
var items = list.getRange(2,2,50)
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(items,true).build();
point.setDataValidation(rule)
}
}
Also, is there a way to have the clear validations to run no matter how many rows there are?
The function will run anytime there's an edit and there's nothing you can do to stop that. You can, instead, terminate execution preemptively if it's not the sheet you care about.
The event object tells you which range was edited. You can get that range's sheet to know which sheet was edited. If the name matches, then execute the other stuff.
function onEdit(e) {
if (e.range.getSheet().getName() === 'Items_Log') {
// Data validation
}
}
It's not great practice to use .getActiveRange() or .getActiveSheet() when you want what was actually edited because there is a chance, however small, that the edited range may differ from the active range at the time of function execution.
Explanation:
You need to take advantage of the event object.
That object contains relevant information to the edits you make.
For example:
e.source is equivalent to SpreadsheetApp.getActive()
e.range is equivalent to .getActiveCell().
To run the code only for a particular sheet, in this case Items_Log, add a condition to check if the name of the active sheet matches that name:
if (current.getColumn()==2 && start.getName()=="Items_Log")
where start is the active sheet:
var start = e.source.getActiveSheet();
Solution:
function onEdit(e) {
var start = e.source.getActiveSheet();
var current = e.range;
var list = e.source.getSheetByName("Indirect_Categ_Ranges")
var main = e.source.getSheetByName("Items_Log");
if (current.getColumn()==2 && start.getName()=="Items_Log")
{
var choice = current.getValue()
list.getRange(2,1).setValue(choice)
main.getRange(2,3,5000).clearDataValidations()
var point = current.offset(0,1)
var items = list.getRange(2,2,50)
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(items,true).build();
point.setDataValidation(rule)
}
}
Thanks for the help. after looking at your suggestions and trying a couple things, I found that simply adding the code:
&& start.getName()=="Items_Log")
To the end of the line:
if (current....
Worked and solved the issue.

Iterate over sheet using GS while there is any data in columns

I need to do a small script, its basic idea is to have a sheet with names in one columns and some random info in another (to the right). The sheet has some count (unknown in advance) of such records, so they are like
John 39483984
George 3498349
Layla 23948
So that a user can enter any number of such simple records, my script must create a file for each name (file is of the same name) and write the number into that file. I managed to find how to create files (though still couldn't find out how to create file in the current folder, same as where the sheet is located - this is just a side question, but if you know how to do it, please tell me). The only real problem is iterating through the records. My idea was to go through them one by one and stop when there is an empty record - basic strategy, but I wasn't able to find how to implement it (yep!). There are range functions, but there I should know in advance the range; also there is a function to get selected cells but that will require a user to select the records, which is strange.
So please suggest me a solution if it exists in this frustration Google Script.
function createFilesForEachNameInSheet() {
// First, you connect to the spreadsheet, and store the connection into a variable
var ss = SpreadsheetApp.openById("SPREADSHEET_KEY_GOES_HERE"); // you do know how to get the spreadsheet key, right?
// Then, you take the sheet from that spreadsheet
var sheet = ss.getSheetByName("Sheet1");
// Then, the "problematic range". You get the ENTIRE range, from end to end, as such:
var wholeRange = sheet.getRange(1,1,sheet.getLastRow(),sheet.getLastColumn());
// Then, you fetch its values:
var rangeValues = wholeRange.getValues();
// At this point you have a bi-dimensional array, representing the rows and columns.
// Assuming you have 2 columns, in the first column you have the names, and in the second you have the unknown value
// You need to use the already known for loop, iterate over all the data, and store it first in an object, so that you create the file only ONCE.
var objectData = {};
for (var i=0;i<rangeValues.length;i++) {
var thisName = rangeValues[i][0];
var thisValue = rangeValues[i][1];
if (objectData.thisName == undefined) objectData.thisName = [];
objectData.thisName.push(thisValue);
}
// Now we have our values grouped by name.
// Let's create a file for each name.
for (var name in objectData) {
DriveApp.createFile(name, objectData.name.join("\n"));
}
// NOTE: if you want to create into a specific folder, you first target it, using the DriveApp:
var folders = DriveApp.getFoldersByName("the folder name goes here");
// folders variable is now an iterator, containing each folder with that name.
// we will iterate over it as follows, and select the one we want.
// the logic for it, you'll select one of your choice:
while (folders.hasNext()) {
var thisFolder = folders.next();
if (/* condition to check if we found the right folder */) {
thisFolder.createFile(name, objectData.name.join("\n"))
}
}
}