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.
Related
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'.
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.
I have mild general programming knowledge but know basically nothing about google apps scripts specifically.
I am trying to create dynamic page breaks in google sheets, or find another way to keep certain rows grouped together when printing.
On my data sheet I have 100s of rows of information and within each row the data can vary significantly in length (from a single number to many paragraphs of text). I have created a second sheet that both filters the information that I want and displays it in a visually-appealing way, taking the original data from each row and parsing it into 8 total rows (7 with information, and one blank to visually separate one block of info from the next) per one original. The problem is that the varying length of the data means I have to manually move the page breaks every time I change the filter.
Here is a blank section of the second sheet for reference.
I want to be able to print with as many 8-row groupings on a page as I can, but not split up a group onto the next page.
I'm honestly not sure how to get started, though I presume that I can use the blank row to trigger the page breaks somehow. Any help would be greatly appreciated!
Updated
I have been able to write some rudimentary code to (mostly) accomplish what I wanted. However the best that I can tell is that getRowHeight() is not working with my wrapped text, as it properly formats when I have any empty data set, but not otherwise.
Can someone confirm, and tell me what I'm missing?
function dynamicPageBreaks() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var curRow = 2; //start on Row 2 (1 contains the filter selection)
var curTotPix = ss.getRowHeight(curRow);
var pgPix = 1030;
//loop until end of sheet
var endRow = ss.getLastRow();
do {
//find row that goes past page break
do {
curRow++;
curTotPix = curTotPix + ss.getRowHeight(curRow);
} while (curTotPix <= pgPix);
//get value of cell in column B of that row
var curCell = sheet.getRange(curRow,2).getValue();
//back up until we find an empty row
if (curCell == "") {
break;
} else do {
curTotPix = curTotPix - ss.getRowHeight(curRow);
curRow = curRow - 1;
curCell = sheet.getRange(curRow,2).getValue();
} while (curCell != "");
//expand empty row to match necessary pixels
var addHeight = pgPix - curTotPix;
ss.setRowHeight(curRow - 1, ss.getRowHeight(curRow) + addHeight);
//reset for next iteration
curTotPix = ss.getRowHeight(curRow);
} while (curRow < endRow);
}
I would recommend you get started by following an Apps script quickstart like the one about extending sheets here[1]
Then you can define your logic using methods from the Spreadsheet service[2].
You have the method getRowHeight()[3] which could help you in determining the length of the range you are looking to print. And that also depends on the paper size you choose.
[1]https://developers.google.com/apps-script/guides/sheets#get_started
[2]https://developers.google.com/apps-script/reference/spreadsheet
[3]https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrowheightrowposition
I have a Google form where some of the fields are "static" content, and some are populated by app script from a Google sheet which will be run on a daily basis. I nearly have it how I want it, but not quite.
I have a couple of dropdown lists that need to contain choices that navigate to different sections of the form, e.g:
Phill / Beacon Hill - Go to section called "Beacon Hill
Jane / Harbord - Go to section called "Harbord"
Fred / Beacon Hill - Go to section called "Beacon Hill"
etc...
What's happening is that instead of appending choices to the list, I'm overwriting which means I only end up with ONE choice in my dropdown, which is the last one added, i.e. Fred in the example above.
I've looked at lots of examples on the web but can't get this to work. I feel I'm very close but not quite there yet. Here's my code with a couple of lines that I believe are the problem. Can someone please tell me where I'm going wrong.
function populatePlayersClubsListV2() {
// ------------------------------------------------------------------------------------------------
// This gets each male player and the junior club they've been assigned to from the
// Junior_Clubs_Training_Sessions s/sheet (which is this s/sheet). It creates a list that the player
// chooses their name from and sets up a branch to the appropriate Club training sessions section
// in the form depending on which club they're assigned to. .
// ------------------------------------------------------------------------------------------------
// Open the "Find Junior Club Training Sessions" form
var form = FormApp.openById("1fo33ncgJY.................iRnMsERRTen8WjTH_xrx0");
// Identify the sheet in this spreadsheet holding the data needed to populate the drop-down. Here
// it's the "PlayersClubs" tab which says which club each player is assigned to.
var ss = SpreadsheetApp.getActive();
var playersClubsSheet = ss.getSheetByName("PlayersClubs");
// Grab the values from the rows & columns of the sheet - use 2 to skip header row. Use getMaxRows
// and getMaxColumns to avoid hard-coding the number of rows and columns.
var playersClubsSsValues = playersClubsSheet.getRange(2, 1, playersClubsSheet.getMaxRows() - 1, playersClubsSheet.getMaxColumns() - 1).getValues();
// We need to loop thro twice - once to populate the male players, and again for the female players.
// Males/females populate different fields and we hold the data-item-IDs of those fields in an array.
var formFieldsArray = [
["Male", 1397567108],
["Female", 1441402031]
];
for(var h = 0; h < formFieldsArray.length; h++) {
// Open the form field you want to populate - it must be a dropdown or multiple choice.
// Right-click field, inspect and look for data-item-ID followed by a number.
var playersClubsFormList = form.getItemById(formFieldsArray[h][1]).asListItem();
// Define array to hold values coming from the s/sheet and used to populate form fields.
var playersClubsArray = [];
var sectionMalePlayers = form.getItemById(309334479).asPageBreakItem();
var sectionFemalePlayers = form.getItemById(856495273).asPageBreakItem();
// Create the array of players and their clubs ignoring empty cells. Check if the s/sheet row
// matches male/female against formFieldsArray[h][0].
for(var i = 0, j = 0; i < playersClubsSsValues.length; i++) {
if(playersClubsSsValues[i][0] != "" && playersClubsSsValues[i][1] == formFieldsArray[h][0]) {
playersClubsArray[j] = playersClubsSsValues[i][0] + " - " + playersClubsSsValues[i][2];
if (formFieldsArray[h][0] = "Male") {
// ** THIS IS THE LINE THAT OVERWRITES BUT I NEED IT TO APPEND *** //
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
}
else {
// ** THIS IS THE LINE THAT OVERWRITES BUT I NEED IT TO APPEND *** //
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionFemalePlayers)]);
}
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
j = j + 1;
} // end if
} // end for loop
} // end for loop for Males/Females
}
Issue:
When setChoices is used, all choices that were previously stored in the item get removed. Only the ones that are specified when using setChoices get added to the item.
Right now, you are only specifying one choice:
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
Solution:
You have to specify all choices you want the item to have. So in this case you would have to do the following:
Retrieve all choices that were previously stored via getChoices. This will retrieve an array with all current choices in the item.
Use Array.prototype.push() to add the choice you want to add to the list of choices.
When using setChoices, the array retrieved in step 1 and modified in step 2 should be provided as the argument.
Code sample:
Change this:
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers)]);
For this:
var choices = playersClubsFromList.getChoices();
choices.push(playersClubsFormList.createChoice(playersClubsArray[j], sectionMalePlayers));
playersClubsFormList.setChoices(choices);
Note:
The same change would have to be made for all the lines where this problem is happening, like this one:
playersClubsFormList.setChoices([playersClubsFormList.createChoice(playersClubsArray[j], sectionFemalePlayers)]);
Reference:
getChoices()
setChoices(choices)
Full disclosure - not a programmer...I've just messed a good bit with Excel and Google Sheets, and am stuck trying to find a simple way to transform a specific set of data. Essentially, we have a website host who provides us with the ability to run an export of the results of various fillable forms. One of them is a registration for e-learning videos. The results of the form provide a specific url for each of about 20 videos we maintain...but neither the url or anything in the form itself automatically indicate a human readable label (like "Intro to Application Use") that's useful if someone wants to use the export track what customer has viewed what specific video. So, for each export I do of the data, I need to find a way to run a macro or script that will run through one column of the data, check it against a key that includes each video-specific url, and then spit out a user-readable name for each video into a second column.
So, I need a script that says, if A1:A100=a,b,c,d,e,f,g ("a,b,c,d,e,f,g" being any entry from a list or urls), then set B1:B100=a*,b*,c*,d*,e*,f*,g* ("a*,b*,c*,d*,e*,f*,g*" being the user readable name of each video represented by the urls).
Any thoughts on this? I think I have a way to do it within a formula in all cells in column B, but I'd be referencing so many lengthy urls in that single formula that it seems absurd not to handle it with a script. I'm just a deadbeat when it comes to scripting...
The Questioner is essentially looking for a means to match a meaningful Movie Title to an un-meaningful (but consistent) url provided by a web service. The Questioner says that they have 100 titles though we have no indication of the volume of transactions. Under the circumstances, and without knowing further volumes, the most efficient option is a linear search.
I created a spreadsheet with two sheets:
1 - Titles: Contains a list of the Supplier URL and the associated meaningful "Title Name". This is a sheet that would be maintained by the questioner as Titles are added or dropped.
2 - Transdata: Contains some same data; includes the Supplier URL, and a column set-aside for the meaningful Title name.
The script involves a nested loop. The first level goes through each row of transaction data. The second, nested loop, evaluates the url for each transaction row and returns the Title, which is saved to the sheet in the "MovieName" Column.
To make it easier to process the function, I've added an OnOpen function so that the Questioner can access the main menu to determine when they process.
function so_52892546() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var Titles = ss.getSheetByName("Titles");
var Titlesrange = Titles.getDataRange();
var Titlesvalues = Titlesrange.getValues();
var TitleslastRow = Titles.getLastRow();
var Trans = ss.getSheetByName("TransData");
var Transrange = Trans.getDataRange();
var Transvalues = Transrange.getValues();
var TranslastRow = Trans.getLastRow();
for (var i = 1; i < TranslastRow; i++) {
for (var z = 1; z < TitleslastRow; z++) {
if (Transvalues[i][0] == Titlesvalues[z][0]) {
var Title = Titlesvalues[z][1];
//Logger.log("match i = "+1);
}
}
//Logger.log("i="+i+". Title: "+MovieTitle+", date = "+Transdata[i][1]+", income"+Moviesdata[i][2]);
var targetrange = Trans.getRange(i + 1, 4);
targetrange.setValue(Title);
}
}
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [{
name: 'Update Movie names',
functionName: 'so_52892546'
}, ];
spreadsheet.addMenu('Update Movies', menuItems);
Screenshot of the Titles sheet
"Before" and "After" screenshots of the Transdata sheet.