Implementing a Dynamic "Lookup Criteria" for a Column in Google Sheets - google-apps-script

In google sheet, I list dropdown options in a column. I get the options of this dropdown from a certain range in the other sheet.
Data validation for single cell(g2)
As you can see in the example, my data validation lookup criterion for the G2 cell is F133 to F260 in the "Lookup" Sheet.
What I want here is to get this data from the next column in the Lookup sheet in each cell that continues as G3-G4-G5.... For instance: "G133-G260" for G3, "H133-H260" for G4.
I can do this manually one by one with creating a individual data validation per cell, but is there a quick way to do it?

I believe your goal is as follows.
You want to create the data validation rules into the cells "G3:G" of the target sheet.
The data validation rules use the range of Lookup!F133:F260, Lookup!G133:G260, Lookup!H133:H260,,, to "G2", "G3", "G4",,, of the target sheet, respectively.
You want to achieve this using Google Apps Script.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Google Spreadsheet and set your target sheet name. And, save the script.
function myFunction() {
const sheetName = "Sheet1"; // Please set your target sheet name.
let offset = 6;
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
const loopupSheet = ss.getSheetByName("Lookup");
const range = sheet.getRange("G2:G" + sheet.getLastRow());
const dataValidations = range.getValues().map(_ => [SpreadsheetApp.newDataValidation().requireValueInRange(loopupSheet.getRange(133, offset++, 128)).build()]);
range.setDataValidations(dataValidations);
}
When this script is run, the ranges of Lookup!F133:F260, Lookup!G133:G260, Lookup!H133:H260,,, are used as the data validation rules, and they are put to the cells "G2", "G3", "G4",,,.
In this case, sheet.getRange("G2:G" + sheet.getLastRow()) is used as the destination range. If you want to manually set the range, please modify it with like sheet.getRange("G2:G10").
let offset = 6; means that the 1st column is column "F". If you want to change this, please modify it.
References:
map()
setDataValidations(rules)

You could do this without script if you're willing to have an auxiliary sheet. Just put in your Auxiliary sheet: =TRANSPOSE(Lookup!G133:260)
Then, go back and select your range in column G. Select all the cells and put in Data Validation from a Range: =Auxiliary!1:1 - Be sure you don't put any "$", that way each cell will go and look in the next row and so on

Related

Google Apps Script to copy range in spreadsheet using sheet reference (not sheet name)

I need to copy a range in a spreadsheet from one row to another - but the name of the sheet changes and I am unable to work out how to use the sheet reference rather than sheet name when referring to the range.
Example code below which works - however I need to tell GAS to go get the first sheet, not sheet "S1"
function copyRange() {
var sheet = SpreadsheetApp.openById("1Ofua0Tzut5fnZRLkXwldy4pq4AVupdvNYybrFGrHjBU").getSheetByName("S1")
var source_range = sheet.getRange("A2:G2");
var target_range = sheet.getRange("A3:G3");
source_range.copyTo(target_range);
}
About Example code below which works - however I need to tell GAS to go get the first sheet, not sheet "S1", in this case, how about the following modification?
From:
var sheet = SpreadsheetApp.openById("1Ofua0Tzut5fnZRLkXwldy4pq4AVupdvNYybrFGrHjBU").getSheetByName("S1")
To:
var sheet = SpreadsheetApp.openById("1Ofua0Tzut5fnZRLkXwldy4pq4AVupdvNYybrFGrHjBU").getSheets()[0];
By this modification, the 1st tab is used as sheet. When [0] of getSheets()[0] is changed to [1], the 2nd sheets is used. When you changed the arangement of the sheet, the sheet is changed. For example, when you move the 1st tab from 3rd tab, getSheets()[0] retrieves the current 1st tab. Please be careful about this.
Reference:
getSheets()

How to set a named range for a data validation programmatically (in Google apps script) in a Google spreadsheet?

Use Case
Example. I have a named range Apples (address "Sheet10!B2:B"), which in use for data validation for plenty of sheet cells. The data range for Apples can be changed (in a script), e.g. to "Sheet10!D2:D".
It works from UI
I can set manually a named range as a data source of data validation.
In this case, the data validation of a cell will always refer to the named range Apples with updated the data range.
How to make it in Google Apps Script?
GAS Limits
The code, for setting data validation, should look like this, if you have a namedRange object:
mySheet.getRange('F5')
.setDataValidation(
SpreadsheetApp.newDataValidation()
.requireValueInRange(
namedRange.getRange()
)
.setAllowInvalid(false)
.build()
);
DataValidationBuilder.requireValueInRange() does not work here as it requires only class Range (it cannot get NamedRange), and no reference to a named range will be used.
Is there a workaround or so?
UPD1 - Spreadsheet.getRangeByName() does not work
Getting range by name does not help, the data validation will get actual range address.
SpreadsheetApp.getActive().getRangeByName("Apples")
UPD2 No way to make it so far in GAS
As #TheMaster posted, it's not possible at this moment.
Please set +1 for posts:
https://issuetracker.google.com/issues/143913035
https://issuetracker.google.com/issues/203557342
P.S. It looks like the only solution will work is Google Sheets API.
I thought that in your situation, I thought that when Sheets API is used, your goal might be able to be used.
Workaround 1:
This workaround uses Sheets API.
Usage:
1. Prepare a Google Spreadsheet.
Please create a new Google Spreadsheet.
From Example. I have a named range Apples (address "Sheet10!B2:B"), which in use for data validation for plenty of sheet cells. The data range for Apples can be changed (in a script), e.g. to "Sheet10!D2:D"., please insert a sheet of "Sheet10" and put sample values to the cells "B2:B" and "D2:D".
Please set the named range Sheet10!B2:B as Apple.
2. Sample script.
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And, please enable Sheets API at Advanced Google services.
function myFunction() {
const namedRangeName = "Apple"; // Please set the name of the named range.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Sheet10");
const requests = [{ updateCells: { range: { sheetId: sheet.getSheetId(), startRowIndex: 0, endRowIndex: 1, startColumnIndex: 0, endColumnIndex: 1 }, rows: [{ values: [{ dataValidation: { condition: { values: [{ userEnteredValue: "=" + namedRangeName }], type: "ONE_OF_RANGE" }, showCustomUi: true } }] }], fields: "dataValidation" } }];
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
In this request, the name of the named range is directly put to userEnteredValue.
3. Testing.
When this script is run to the above sample Spreadsheet, the following result is obtained.
When this demonstration is seen, first, you can see the named range of "Apple" which has the cells "B1:B1000". When a script is run, data validation is put to the cell "A1" with the named range of "Apple". In this case, the values of data validation indicate "B1:B1000". When the range named range "Apple" is changed from "B1:B1000" to "D1:D1000" and the data validation of "A1" is confirmed, it is found that the values are changed from "B1:B1000" to "D1:D1000".
Workaround 2:
This workaround uses the Google Spreadsheet service (SpreadsheetApp). In the current stage, it seems that the Google Spreadsheet service (SpreadsheetApp) cannot directly achieve your goal. This has already been mentioned in the discussions in the comment and TheMaster's answer. When you want to achieve this, how about checking whether the range of the named range is changed using OnChange as following workaround 2?
Usage:
1. Prepare a Google Spreadsheet.
Please create a new Google Spreadsheet.
From Example. I have a named range Apples (address "Sheet10!B2:B"), which in use for data validation for plenty of sheet cells. The data range for Apples can be changed (in a script), e.g. to "Sheet10!D2:D"., please insert a sheet of "Sheet10" and put sample values to the cells "B2:B" and "D2:D".
Please set the named range Sheet10!B2:B as Apple.
2. Sample script.
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And, please install OnChange trigger to the function onChange.
First, please run createDataValidation. By this, data validation is put to the cell "A1" of "Sheet10". In this case, the set range is the range retrieved from the named range "Apple". So, in this case, the range is Sheet10!B2:B1000.
As the next step, please change the range of the named range from Sheet10!B2:B1000 to Sheet10!D2:D1000. By this, onChange` function is automatically run by the installed OnChange trigger. By this, the data validation of "A2" is updated. By this, the values of data validation are changed.
const namedRangeName = "Apple"; // Please set the name of the named range.
const datavalidationCell = "Sheet10!A2"; // As a sample. data validation is put to this cell.
function onChange(e) {
if (e.changeType != "OTHER") return;
const range = e.source.getRangeByName(namedRangeName);
const a1Notation = `'${range.getSheet().getSheetName()}'!${range.getA1Notation()}`;
const prop = PropertiesService.getScriptProperties();
const previousRange = prop.getProperty("previousRange");
if (previousRange != a1Notation) {
const rule = SpreadsheetApp.newDataValidation().requireValueInRange(e.source.getRangeByName(namedRangeName)).setAllowInvalid(false).build();
e.source.getRange(datavalidationCell).setDataValidation(rule);
}
prop.setProperty("previousRange", a1Notation);
}
// First, please run this function.
function createDataValidation() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const rule = SpreadsheetApp.newDataValidation().requireValueInRange(ss.getRangeByName(namedRangeName)).setAllowInvalid(false).build();
ss.getRange(datavalidationCell).setDataValidation(rule);
const prop = PropertiesService.getScriptProperties();
const range = ss.getRangeByName(namedRangeName);
const a1Notation = `'${range.getSheet().getSheetName()}'!${range.getA1Notation()}`;
prop.setProperty("previousRange", a1Notation);
}
References:
Method: spreadsheets.batchUpdate
UpdateCellsRequest
DataValidationRule
Currently, This seems to be impossible. This is however a known issue. +1 this feature request, if you want this implemented.
https://issuetracker.google.com/issues/143913035
Workarounds from the tracker issue creator:
If a validation rule is manually created with a NamedRange via the Sheets GUI, it can then be copied programmatically using Range.getDataValidations(), and subsequently used to programmatically create new DataValidations. DataValidations created this way maintain their connection to the NamedRange, and behave like their manually created counterparts. This demonstrates that the functionality to 'use' NamedRanges for data validation rules is already possible with Apps Scripts, but not the option to 'create' them.
As a half-answer, if you want just validation and can live without the drop-down list of valid values, you can programmatically set a custom formula that references the named range. This reference to the named range will not get expanded in the AppsScript, so future changes to the Named Range's actual range will percolate to the validator. Like so:
mySheet.getRange('F5')
.setDataValidation(
SpreadsheetApp.newDataValidation()
.requireFormulaSatisfied(
'=EQ(F5, VLOOKUP(F5, ' + namedRange.getName() + ', 1))'
)
.setAllowInvalid(false)
.build()
);
(The formula just checks that the value in the cell being tested is equal to what VLOOKUP finds for that cell, in the first column -- I'm assuming the named range content is sorted.)
Use getRangeByName()
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
var cell = sh.getRange(1, 10);//location where datavalidation is applied
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(ss.getRangeByName("MyList")).build();
cell.setDataValidation(rule);
}

Is it possible to add formatting (shading) to rows being appended in Google Sheets (by Google Apps Script)

I've got a Google App Script which is copying rows from one sheet to another, performing various transformations. This logic ultimately gets rows onto the new sheet using sheet.appendRow(row detail). I would like these newly created rows to have a background colour (my intention is to hold a 'latestColour' so I can alternate the shading).
So, is there anyway to add shading within the appendRow method itself, or easily determine the range that the appendRow method processed, such that I can apply additional logic to add the shading.
You can use conditional formatting
=and(A1<>"",A2="")
Although I'm not sure whether I could correctly understand your situation, from your question, I thought that you might be using [Format] --> [Alternating colors] in Google Spreadsheet. And, when a new row is appended by putting the values, you might want to reflect "Alternating colors" in the appended row. If my guess is correct, how about the following sample script?
Sample script:
function myFunction() {
const addValues = ["sample1", "sample2", "sample3"]; // This is a sample appending value. Please replace this for your value.
const sheetName = "Sheet1"; // Please set the sheet name.
// Retrieve banding object from the data range.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const b = sheet.getDataRange().getBandings();
if (b.length == 0) {
console.log("Bandings are not used.");
return;
}
// Append the value.
sheet.appendRow(addValues);
// Expand the range of banding.
b[0].setRange(sheet.getDataRange());
}
When this script is run, the current banding is retrieved. And, after the value was appended, the banding is updated by including the appended row. In this sample, even when the multiple rows are appended, this script can be used.
Note:
From your question, I guessed that there is one banding in the data range in your sheet. Please be careful this.
References:
getBandings()
setRange(range)
Unfortunately the method appendRow() does not receive formatting settings as input, only an array of values.
However, here is a suggestion if you want to implement your own logic:
Sample code:
function applyColorLastRow() {
var ss = SpreadsheetApp.getActive(); //get active sheets file
var range = ss.getDataRange(); //get populated range, you may want to set a range manually if needed.
var lastRowNum = range.getLastRow(); //getting the last row index of the range.
var lastRowRange = ss.getRange(`${lastRowNum}:${lastRowNum}`); //narrowing the range (using A1 notation) to the last row only to apply color
var lastRowColor = lastRowRange.getCell(1,1).getBackgroundObject().asRgbColor().asHexString();
//Your row coloring logic here...
if (lastRowColor === '#ffffff'){ //toggling white/grey color as an example...
lastRowRange.setBackground('#cccccc'); //apply grey color to all cells in the last row range
} else {
lastRowRange.setBackground('#ffffff'); //apply white color to all cells in the last row range
};
}

Can i have a simple script on sheet1 and sheet2?

I have made a script that automatically sorts data when i make a change. It workds fine on the one sheet but i cannot find a way to apply it on sheet2 too.
So i need it to sort both sheet1 and 2 (the sheets are simular so the same sort range and everything needs to be the same - i just need it to apply on sheet2 also-
I have tried to make a copy of the code and change sheet_name to sheet2.
I Insert picture of the code. I would be very happy if you could help.
Explanation:
Take advantage of the event object. This will give you very important information regarding the edit/event which took place.
The idea is to use the script for multiple sheets. One way to do that is to construct an array of the sheet names you want to include:
const sheet_names = ["Management Associate","Finance Associate"];
and check if the name of the active sheet is included in this list:
if(sheet_names.includes(sheet.getName())){
// the rest of the code here
}
As a more complete implementation, I modified the last line of your code to include the name of the sheet that was edited. To do that, I used template literals but of course this approach is optional:
ss.toast(`Sort complete in ${sheet.getName()}`);
Solution:
function onEdit(e) {
const sheet_names = ["Management Associate","Finance Associate"]; // add sheets here
const sort_data_range = "A2:H999";
const sort_order = [{column:6, ascending:true}];
const ss = e.source;
const sheet = ss.getActiveSheet();
if(sheet_names.includes(sheet.getName())){
const range = sheet.getRange(sort_data_range);
range.sort(sort_order);
}
ss.toast(`Sort complete in ${sheet.getName()}`);
}

Google Apps Script/Google Sheet Bug? Copying/Re-Setup Data Validation refering to named range does not work on duplicated Sheets

SEE THIS EXAMPLE HERE
Situation:
Data validation on cells that refer to a range via direct reference (A1Notation) and Data Validation on other cells that refer to a named range.
Works both.
When duplicating the sheet, both still work. However, when resetting the data validations manually to the same ranges or via my script, the data validation of the cell where the data validation refers via named range will not work and there is no way to set it up again by referring via named range either manually or by copying (.getDataValidation / .setDataValidation) via script.
Check out the example - run script function cp_dataValidation() either on Sheet1 and Copy of Sheet1 as an active sheet. Then click the data validation dropdowns in the cells.
Eventually, my goal is to perform a copy of the data validation (referring to a named range) from one range to another on a duplicated sheet via script.
function cp_dataValidation() {
var sheet = SpreadsheetApp.getActiveSheet()
//cell with data validation referring to a named range
var named_range_tmpl = sheet.getRange("B2");
//cell with data validation referring to a range via A1Notation
var range_tmpl = sheet.getRange("C2");
//target cells to copy dataValidation to
var nr_target = sheet.getRange("D2");
var r_target = sheet.getRange("E2");
nr_target.setDataValidation(named_range_tmpl.getDataValidation());
r_target.setDataValidation(range_tmpl.getDataValidation());
Logger.log(JSON.stringify(named_range_tmpl.getDataValidation(), null, 2));
Logger.log(JSON.stringify(range_tmpl.getDataValidation(), null, 2));
}
You want to copy the Data Validation including the named range as the values.
If my understanding is correct, how about this workaround? It seems that nr_target.setDataValidation(named_range_tmpl.getDataValidation()) cannot be used for the named range of other sheet. So as a workaround, how about using the method of copyTo()?
Modified script:
var sheet = SpreadsheetApp.getActiveSheet();
var source = sheet.getRange("B2:C2");
var target = sheet.getRange("D2:E2");
source.copyTo(target, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION);
target.clearContent(); // or target.clear({contentsOnly: true}); // Added
Note:
Please test the script for both sheets.
If you want to copy both the value and Data Validation, please use source.copyTo(target) instead of source.copyTo(target, SpreadsheetApp.CopyPasteType.PASTE_DATA_VALIDATION).
Reference:
copyTo(destination, copyPasteType, transposed)