What I am trying to make is a workbook that users can select from a dropdown of a group of items (Sheet1, Column A) and then Row B lookup that selected item in sheet "Dataset" and return that value with integers that go from 0 to the corresponding stock quantity total in (Sheet "Dataset" column C)
here is a Sample spreadsheet
I got some awesome code from #iamblichus that will fill the dropdowns from the corresponding stock quantity
see his code here that I have somewhat implemented Here using a query formula to lookup the group stock quantities. I'm not sure how to make this happen across two sheets though.
Answer:
Extending the code that #iamblichus provided here, you can specify the Sheets from which to get the data from and use an onEdit() trigger to automatically change the dropdown when the cell is edited.
Code:
Attach this to the Sample Spreadsheet you provided:
function onEdit(e) {
var ss = SpreadsheetApp.getActive(); // Get the spreadsheet bound to this script
var dataSetSheet = ss.getSheetByName("Dataset"); // Get the sheet called "Working with script" (change if necessary)
var fillSheet = ss.getSheetByName("Sheet 1");
// Get the different values in column C (stock quantities):
var firstRow = 3;
var firstCol = 3;
var numRows = dataSetSheet.getLastRow() - firstRow + 1;
var stockQuantities = dataSetSheet.getRange(firstRow, firstCol, numRows).getValues();
var stockNames = dataSetSheet.getRange(firstRow, firstCol - 1, numRows).getValues();
// Iterate through all values in column:
for (var i = 0; i < stockQuantities.length; i++) {
Logger.log(stockNames);
Logger.log(stockQuantities);
var stockQuantity = stockQuantities[i][0];
var values = [];
// Create the different options for the dropdown based on the value in column C:
if (stockNames[i] == e.value) {
for (var j = 0; j <= stockQuantity; j++) {
values.push(j);
}
// Create the data validation:
var rule = SpreadsheetApp.newDataValidation().requireValueInList(values).build();
// Add the data validation to the corresponding cell in column B:
fillSheet.getRange(e.range.getRow(), 2).clear();
var dropdownCell = fillSheet.getRange(e.range.getRow(), 2).setDataValidation(rule);
}
}
}
Something to note:
I have put this as an onEdit() function because SpreadsheetApp is called in Read Only mode when inside a custom function and so no set*() methods can be called. This includes setDataValidation().
As per the Documentation, the Spreadsheet service is supported, however under 'Notes' it reads:
Read only (can use most get*() methods, but not set*()).
Cannot open other spreadsheets (SpreadsheetApp.openById() or SpreadsheetApp.openByUrl()).
References:
Stack Overflow - Dropdown auto generating a range based on a total
Google Apps Script - Range.clear() method
Google Apps Script - Simple Triggers
Google Apps Script - Custom Functions in Google Sheets
Related
I have a Google Sheet file linked to a Google Form, that I use to record registrations for classes by different teachers. Inside of it I create extra columns that mark "X" in a row, when someone registers for that particular class, using an =IF function. I'd like those "X"'s to be checkboxes so that I can check attendees in the sheet itself as they arrive, but there is no method I can find for a function to output a tickbox into the cell and I have next to no knowledge of JavaScript.
Example Sheet: https://docs.google.com/spreadsheets/d/19BUkEfo8dWcAfPDhmhBTuhC1-V-QROlfF0pWH75c9VA/edit?usp=sharing
Ideally, I'd have a custom function that basically does the same as my =IF but inserts a checkbox instead of writing "X" (ie. if cell A contains text from cell B insert checkbox, else leave empty).
Alternatively I also found this solution to a similar problem, that I think would work, but I don't know enough to tweak it to my needs. This way I'd keep my sheet as is and the script would just replace the X's with checkboxes?
Probably it is not very efficient but it works:
// function creates menu 'SCRIPTS'
function onOpen() {
SpreadsheetApp.getUi().createMenu('SCRIPTS')
.addItem('Insert checkboxes', 'replace_x_to_checkboxes')
.addToUi();
}
// function replaces all 'X' on the sheet with checkboxes
function replace_x_to_checkboxes() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var data = range.getValues();
for (var row in data)
for (var col in data[row]) {
if (data[row][col] == 'X') sheet.getRange(+row+1,+col+1).insertCheckboxes()
}
}
To insert checkboxes without using formulas and 'X's you can run this function:
function insert_checkboxes() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var b1 = data[0][1]; // name from cell 'B1'
var c1 = data[0][2]; // name from cell 'C1'
for (var row=1; row<data.length; row++) {
if (data[row][3].indexOf(b1) > -1) sheet.getRange(row+1,2).insertCheckboxes();
if (data[row][3].indexOf(c1) > -1) sheet.getRange(row+1,3).insertCheckboxes();
}
}
Just in case. Instead of data[row][3].indexOf(b1) > -1 you can use a modern variant of the same condition data[row][3].includes(b1))
I am looking for help to create a filter on Google Sheets Script.
I want the following:
I have a database Schedule which has table with information that I want to filter
Once the database A is updated I want to filter Colum b row 8 and only take the cells that have information and are filled with words, numbers, etc.
After that I want to copy the data filtered and paste on a new Sheet Data “Foreman on specific columns
Also, I want to copy and paste the format such as color, size, etc.
I have the following code which it does the partial job but I cannot figure it out to only get the specific data needed
function Foreman(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var scheduleCCC_sheet = ss.getSheetByName("Schedule");
var Foreman_sheet = ss.getSheetByName("Foreman");
var pasteforemans = Foreman_sheet.getRange(8,2);
var originalData = scheduleCCC_sheet.getRange(9,2,scheduleCCC_sheet.getLastRow()1,11).getValues();
var filter1 ="";
Foreman_sheet.getRange(8,2,Foreman_sheet.getLastRow(),11).clearContent().clearFormat();
var data = originalData.filter(filterlogic);
Foreman_sheet.getRange(9,2,data.length,data[0].length).setValues(data);
}
var filterlogic = function(item){
if(item[1] == ""){
return false;
}else {
return true;
}
In order to filter the values based on the condition that the cells are not empty, you can use the below snippet of code:
Snippet
function myFunction() {
var ss = SpreadsheetApp.openById("ID_OF_THE_SS").getSheetByName("Schedule CCC");
var otherss = SpreadsheetApp.openById("ID_OF_THE_SS").getSheetByName("Foreman");
var range = ss.getRange("START_ROW", "START_COL", ss.getLastRow(), ss.getLastColumn());
var filter = range.getFilter();
var filterCriteria = SpreadsheetApp.newFilterCriteria().whenCellNotEmpty().build();
filter.setColumnFilterCriteria("COL_POSITION", filterCriteria);
for (var i = 1; i <= ss.getLastRow(); i++) {
if (ss.isRowHiddenByFilter(i) == false) {
ss.getRange("RANGE_FROM_THE_CCC_SHEET").copyTo(otherss.getRange("RANGE_FROM_THE_FOREMAN_SHEET"));
}
}
}
Explanation
The above code gathers the filter from the source sheet and sets it the whenCellNotEmpty criteria. Afterwards it loops through the data and checks if a specific row is hidden by the filter; if the result of this is false, then it copies the row with its format onto the destination sheet. The format of the row is preserved by using the copyTo method.
Note
You will need to adjust the ranges to match the ones in your sheet and might need to add another condition when looping through the data from the source sheet.
Reference
Apps Script Sheet Class - isRowHiddenByFilter(rowPosition);
Apps Script Filet Class - setColumnFilterCriteria(columnPosition, filterCriteria);
Apps Script Sheet Class - copyTo(spreadsheet).
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 looked through all of the questions, and I can't quite figure out what I am doing wrong. Here is the background:
I created a spreadsheet that has multiple tabs. Each tab corresponds to types of events (e.g parades, festivals, sports). Each of these tabs is used to assign specific job numbers to a specific event. Parade job numbers are in the 2000 range, festivals 3000 range, sports 4000 range.
I then have a different tab which defines an array that collects all of the job numbers and events, and sorts them alphabetically. This tab is called "Active_Job_Numbers".
My goal is to create several forms that call to this spreadsheet for the values on the "Active_Job_Numbers" tab. I sort of have this working, but not completely. Each form has a purpose such as submitting a request to accounting to process a credit card transaction; another form to submit a request to cut a check.
All of these requests have to be tied to an ever changing list of job numbers. When I update the spreadsheet, the dropdown in the spreadsheet will populate based on the "Active_Job_Numbers" data.
The code that I am using does work, BUT if I update a job number or event name on the spreadsheet, the form still sees the old value. I have triggers set up for onSubmit and onOpen and still, the only way I can get the dropdown in the form to update is if I change one of the "var" labels. Then save it. Then reopen the form. This is a showstopper.
Here is the code I am using:
function updateForm(){
// call your form and connect to the drop-down item
var creditCardRequestGoogleForm = FormApp.openById("1LqtPLGEhocGdkg75mkdldfplxSxWJmYZCp3u1bMwVQQ");
var jobNumberDropDown = creditCardRequestGoogleForm.getItemById("1632374512").asListItem();
// identify the sheet where the data resides needed to populate the drop-down
var ss = SpreadsheetApp.openById("17fgel9Jfl4Aske85mfkwsphBQpZgtGhR8Q");
SpreadsheetApp.setActiveSpreadsheet(ss);
var activeJobNumbers = ss.getSheetByName('Active_Job_Numbers');
// grab the values in the first column of the sheet - use 2 to skip header row
var jobNumValues = activeJobNumbers.getRange(2, 1, activeJobNumbers.getMaxRows() - 1).getValues();
var jobNums = [];
// convert the array ignoring empty cells
for(var i = 0; i < jobNumValues.length; i++)
if(jobNumValues[i][0] != "")
jobNums[i] = jobNumValues[i][0];
// populate the drop-down with the array data
jobNumberDropDown.setChoiceValues(jobNums);
}
A separate but related problem...there are some forms I have that have two different drop-down elements that call to the same spreadsheet but to different tabs. I am not sure how to group this script so I have to separated out like so:
function updateForm(){
// call your form and connect to the drop-down item
var ccrForm = FormApp.openById("1r5n6Sr7fhtGCqht_QHy9qWR1v2ESabxdE-bMxjR-M");
var jobNumberDropDown = ccrForm.getItemById("1678955392").asListItem();
// identify the sheet where the data resides needed to populate the drop-down
var ss = SpreadsheetApp.openById("17eGAlcnBB7ejdfqp3HBQBK9wJAKWYhBQpZgtGhR8Q");
SpreadsheetApp.setActiveSpreadsheet(ss);
var currentJobNumbers = ss.getSheetByName('Active Job Numbers');
// grab the values in the first column of the sheet - use 2 to skip header row
var jobNumValues = currentJobNumbers.getRange(2, 1, currentJobNumbers.getMaxRows() - 1).getValues();
var jobNums = [];
// convert the array ignoring empty cells
for(var i = 0; i < jobNumValues.length; i++)
if(jobNumValues[i][0] != "")
jobNums[i] = jobNumValues[i][0];
// populate the drop-down with the array data
jobNumberDropDown.setChoiceValues(jobNums);
}
function updateForm(){
// This call your form and connect to the drop-down item
var form = FormApp.openById("1r5n6Sr7fhtGCqht_QHy9qWR1v2ESabxdE-bMxjR-M");
var poNumberDropDown = form.getItemById("57865789991").asListItem();
// This is the second dropdown which calls the same spreadsheet but different tab NEED HELP COMBINING THESE TWO MORE ELEGANTLY
var ss = SpreadsheetApp.openById("17eGAlcnBB7ejdfqp3HBQBK9wJAKWYhBQpZgtGhR8Q");
SpreadsheetApp.setActiveSpreadsheet(ss);
var currentPONumbers = ss.getSheetByName('PO_Number');
// grab the values in the first column of the sheet - use 2 to skip header row
var poNumValues = currentPONumbers.getRange(2, 1, currentPONumbers.getMaxRows() - 1).getValues();
var poNums = [];
// convert the array ignoring empty cells
for(var i = 0; i < poNumValues.length; i++)
if(poNumValues[i][0] != "")
poNums[i] = poNumValues[i][0];
// populate the drop-down with the array data
poNumberDropDown.setChoiceValues(poNums);
}
Any insight would be much appreciated.
onOpen trigger for a form, as mentioned in documentation:
This event does not occur when a user opens a form to respond, but
rather when an editor opens the form to modify it
Instead, you can use an installable trigger to activate your code when the sheet is edited.
Furthermore, instead of modifying all form each time an edit is made on the spreadsheet. You can check for which sheet is modified in the spreadsheet to modify the corresponding form. To determine the which sheet is modified you can use event objects.
Here is example code, you can modify this for your use.
function installedEdit(evtObj){
Logger.log("Edit running")
var sheetName = evtObj.range.getSheet().getSheetName()
Logger.log(sheetName)
var itemId="None"
switch (sheetName){
case "Active Job Numbers":
itemId = "1678955392"
updateForm(sheetName,itemId)
break;
case "PO_Number":
itemId = "57865789991"
updateForm(sheetName,itemId)
break;
}
}
function updateForm(sheetName,itemId){
// call your form and connect to the drop-down item
var form = FormApp.openById("1r5n6Sr7fhtGCqht_QHy9qWR1v2ESabxdE-bMxjR-M");
var dropDown = form.getItemById(itemId).asListItem();
// identify the sheet where the data resides needed to populate the drop-down
var ss = SpreadsheetApp.openById("17eGAlcnBB7ejdfqp3HBQBK9wJAKWYhBQpZgtGhR8Q");
SpreadsheetApp.setActiveSpreadsheet(ss);
var sheet = ss.getSheetByName(sheetName);
// grab the values in the first column of the sheet - use 2 to skip header row
var choiceValues = sheet.getRange(2, 1, sheet.getLastRow() - 2).getValues();
var choices = [];
// convert the array ignoring empty cells
for(var i = 0; i < choiceValues.length; i++)
if(choiceValues[i][0] != "")
choices[i] = choiceValues[i][0];
// populate the drop-down with the array data
dropDown.setChoiceValues(choices);
}
Note: Make sure installedEdit() is set up to run when the spreadsheet is edited and is bound to the spreadsheet.
Is it possible to get the filter criteria of my data set. For example, if one of my column is "Department" and I filtered the data to display only "IT". How do we get the filtered criteria, "IT". I need to get that filtered criteria into my GAS to do some other manipulation.
Thanks.
Try this example for a simple filter. It will filter information (change XXX to something meaningful) from the first column:
function onOpen(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var filterMenuEntries = [{name: "filter Column1", functionName: "filter"}];
ss.addMenu("choose filter", filterMenuEntries);
}
function filter(){
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
for (var i=1; i <=numRows -1; i++){
var row =values[i];
// Column value
var myValue = row[0];
// filter value
if (myValue == "XXX"){
sheet.hideRows(i+1);
}
}
}
Google Spreadsheet already has a FILTER formula (I always use this page to remind me how to do it). So for example if your data looked like this
A
1 Department
2 IT Department (Edinburgh)
3 Department of IT
4 Other Department
to get a filtered list you could use the formula
=FILTER(A:A;FIND("IT",A:A)>0)
(Working example here)
If you want to do something entirely in Apps Script Romain Vialard has written a Managed Library with a filter function. Here are instructions for installing and using the 2D Array2 library
Filters are now available using with the recent launch of the Advanced Sheets Service:
Here is a link to the article
Here is a little snippet of how to do :
function setSheetBasicFilter(ssId, BasicFilterSettings) {
//requests is an array of batchrequests, here we only use setBasicFilter
var requests = [
{
"setBasicFilter": {
"filter": BasicFilterSettings
}
}
];
Sheets.Spreadsheets.batchUpdate({'requests': requests}, ssId);
}
In search for an answer to this question, I came up with the next workaround:
apply filters in the sheet;
color the filtered (and therefore
visible) cells (in the example code red in column F);
run script:
reading background colors (of column F) in array colors
iterating this array
building up the new array of row numbers that are visible.
This last array can be used to further manipulations of the sheet data.
To vivualize the effect of the script, I made the script setting the background of used cells to green.
It's one extra small effort for the end user to make, but IMHO it's the only way to make it possible to only use the filtered data.
function getFilterdData(){
var s = SpreadsheetApp.getActive();
var sheet= s.getSheetByName('Opdrachten en aanvragen');//any sheet
var rows = new Array();
var colors = sheet.getRange(1, 6, sheet.getLastRow(), 1).getBackgrounds();
for(var i = 0; i < colors.length; i++){
if(colors[i] == "#ff0000"){
var rowsIndex = rows.length;
rows[rowsIndex] = i+1;
sheet.getRange(i+1, 6).setBackground("#d9ead3")
}
}
}
Based on mhawksey answer, I wrote the following function:
//
function celdasOcultas(ssId, rangeA1) {
// limit what's returned from the API
var fields = "sheets(data(rowMetadata(hiddenByFilter)),properties/sheetId)";
var sheets = Sheets.Spreadsheets.get(ssId, {ranges: rangeA1, fields: fields}).sheets;
if (sheets) {
return sheets[0].data[0].rowMetadata;
}
}
then called it this way:
// i.e. : ss = spreadsheet, ranges = "Hoja 2!A2:A16"
Logger.log(range.getA1Notation());
var ocultas = celdasOcultas(ss.getId(), sheetName + "!" + range.getA1Notation());
// Logger.log(ocultas);
var values = range.getValues();
for (var r = 0; r < values.length; r++) {
if (!ocultas[r].hiddenByFilter) {
values[r].forEach( function col(c){
Logger.log(c);
});
}
//range.setBackground("lightcoral");
}
Than prevent the log of the hidden rows. You can see it in action at:
Prueba script, note project has to get Google Sheets API enabled on Google API Console.
Hope it helps. Thank you!
This as raised in Google's Issue Tracker as Issue #36753410.
As of 2018-04-12, they have posted a solution:
Yesterday we released some new functionality that makes it possible to
manipulate filters with Apps Script:
https://developers.google.com/apps-script/reference/spreadsheet/range#createFilter()
https://developers.google.com/apps-script/reference/spreadsheet/sheet#getfilter
https://developers.google.com/apps-script/reference/spreadsheet/filter
Unfortunately Apps Script still doesn't have methods for determining
if a given row or column is hidden by the filter. See comment #157 for
a workaround utilizing the Sheets Advanced Service.
The Filter class lets you get the FilterCriteria for each column in turn.