Script based DataValidation not getting all the values (Sheets) - javascript

This code was working and although I don't recall having changed anything structure wise, it stopped working partially.
Once I select an item on cell W4, it is apparently only giving me the first option from another sheet on column C related to that item, while that the dropdown list should have about 04 items. I can't find where the flaw is:
function onEdit(){
var tabLists = "ArquivoItens";
var tabValidation = "EditarItem";
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var datass = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(tabLists);
var activeCell = ss.getActiveCell();
//It checks if the active cell is where the product name is
if(activeCell.getColumn() == 23 && activeCell.getRow() == 4 && ss.getSheetName() == tabValidation){
//if it is, it clears the Versão cell, so you can choose the ones related to the product of choice.
activeCell.offset(0, 6).clearContent().clearDataValidations();
//specifies the data range where the Product and the related field you want filtered is on
var makes = datass.getRange(2, 1, datass.getLastRow(), 3).getValues();
Logger.log(makes);
//this is what I need to study - map function
var searchArray = makes.map(function(e){return e[1];});
var makeIndex = searchArray.indexOf(activeCell.getValue());
Logger.log(searchArray);
//if anything on B (where desired column data is) is found related to A, where the product sits
if(makeIndex != -1){
//push found items to the datavalidation list
var validationRange = datass.getRange("C" + (2+makeIndex));
var validationRule = SpreadsheetApp.newDataValidation().requireValueInRange(validationRange).build();
activeCell.offset(0, 6).setDataValidation(validationRule);
}
}
}
Thanks a lot for helping me build the solutions I need, while learning tons on js.
Cheers,

indexOf() finds only the first instance of a search key
If you expect activeCell.getValue() to be present more than once, you either need to implement a loop where you call indexOf() multiple times, or - easier - use the Apps Script method createTextFinder().findAll().
Also, if your values of interest are not contained in an adjacent range, you cannot use requireValueInRange(). Use instead requireValueInList().
Sample:
...
if(activeCell.getColumn() == 23 && activeCell.getRow() == 4 && ss.getSheetName() == tabValidation){
var makes = datass.getRange(2, 1, datass.getLastRow(), 3).getValues();
var validationList = [];
var searchArray = datass.getRange(2, 2, datass.getLastRow(), 1).createTextFinder(activeCell.getValue()).findAll();
for(var i = 0; i < searchArray.length; i++){
var row = searchArray[i].getRow();
var Cvalue = makes[row-1][2];
validationList.push(Cvalue);
}
if(validationList.length > 0){
var validationRule = SpreadsheetApp.newDataValidation().requireValueInList(validationList).build();
activeCell.offset(0, 6).setDataValidation(validationRule);
}
}
...

My limited knowledge doesn't allow me to play around with advanced scripting, so I looked ffor another approach and here's how I ended up getting this to work:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ArquivoItens");
var versionList = new Array();
var activeCell = ss.getActiveCell();
if(activeCell.getColumn() == 23 && activeCell.getRow() == 4 && ss.getSheetName() == "EditarItem"){
activeCell.offset(0, 6).clearContent().clearDataValidations();
var dataRng = dataSheet.getSheetValues(2, 2, dataSheet.getLastRow(), 2);
for (var i = 0; i < dataRng.length; i++){
if(dataRng[i].indexOf(activeCell.getValue())!=-1){
versionList.push(dataRng[i][1])
}
}
var validation = SpreadsheetApp.newDataValidation();
validation.setAllowInvalid(false);
validation.requireValueInList(versionList, true);
activeCell.offset(0, 6).setDataValidation(validation.build());
}
}
Thanks a lot for your help!

Related

Google Appscript transpose dynamic data group from one column

I've been jogging my brain trying to figure out how to write this script to transpose data from one sheet to another from a pretty dirty sheet.
There are other questions like this but none seem to be like my particular use case.
This is how the sheet is currently structured (somewhat):
The biggest issue here is I have no concrete idea how many rows a particular group of data will be, But I know there are always a bunch of blank rows between each group of data.
I found a script that took me half way:
function myFunction() {
//Get values of all nonEmpty cells
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("D:D").getValues().filter(String);
//Create object with 3 columns max
var pasteValues = [];
var row = ["","",""];
for (i = 1; i<values.length+1; i++){
row.splice((i%3)-1,1,values[i-1]);
if(i%3 == 0){
pasteValues.push(row);
var row = ["","",""]
}
}
if(row != []){
pasteValues.push(row)
}
//Paste the object in columns A to C
ss.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
But in that case the asker dataset was fixed. I can loosely say that the max number of rows each group would have is 10(this is an assumption after browsing 3000 rows of the sheet...but if the script can know this automatically then it would be more dynamic). So with that in mind...and after butchering the script...I came up with this...which in no way works how it should currently(not all the data is being copied):
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues().filter(Array);
var pasteValues = [];
var row = [];
for (i = 1; i<values.length; i++){
if(values[i] != ""){
row.push(values[i])
}
Logger.log(row);
if(i%10 == 0){
pasteValues.push(row);
row = []
}
}
if(row != []){
pasteValues.push(row)
}
copyto.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
I'm pretty sure I should maybe still be using array.splice() but haven't been successful trying to implement it achieve what i want, here's how the transposed sheet should look:
Info:
Each group of addresses inside the "copyfrom" sheet would be separated by at least 1 blank line
The length of an address group is not static, some can have 5 rows, others can have 8, but address groups are always separated by blank rows
Any help is appreciated
You are right to iterate all input values, and I can suggest the similar code:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues();
var pasteValues = [[]]; // keep result data here
values.forEach(function(v) { // Iterate all input values
// The last row to be filled in currently
var row = pasteValues[pasteValues.length - 1];
if (v[0]) {
row.push(v[0]);
} else if (row.length > 0) {
while (row.length < 10) {
row.push(''); // Adjust row length
}
pasteValues.push([]);
}
});
if (pasteValues[pasteValues.length - 1].length == 0) pasteValues.pop();
copyto.getRange(1, 1, pasteValues.length, pasteValues[0].length).setValues(pasteValues);
}
Solution:
Assuming that every new row begins with Name, you can use this script to rearrange the column:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyFrom');
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyTo');
var lastRow = copyfrom.getLastRow();
var values = copyfrom.getRange(1,1,lastRow).getValues().filter(Array);
var pasteValues = [];
var row = [];
var maxLen = 1;
// rearrange rows
for (i = 0; i < values.length; i++) {
if (values[i] == "Name" && i > 0) {
pasteValues.push(row);
row = [values[i]];
}
else if (values[i] != "") {
row.push(values[i]);
if (row.length > maxLen) {
maxLen = row.length;
}
}
}
pasteValues.push(row);
// append spaces to make the row lengths the same
for (j = 0; j < pasteValues.length; j++) {
while (pasteValues[j].length < maxLen) {
pasteValues[j].push('');
}
}
copyto.getRange(1,1,pasteValues.length,maxLen).setValues(pasteValues);
}
Sample I/O:
As far as I can tell, there is no way to get the columns to line up in the output since you don't have any way to tell the difference between, for example, an "address 2" and a "City".
However, as far as merely grouping and transposing each address. This one formula, in one cell, in the tab here called MK.Help will work from the data you provided. It will work for as many contacts as you have.
=ARRAYFORMULA(QUERY(QUERY({A:A,IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),COUNTIFS(IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),A:A,"<>",ROW(A:A),"<="&ROW(A:A))},"select MAX(Col1) where Col1<>'' group by Col2 pivot Col3",0),"offset 1",0))

Locating all cell's positions in google sheets

The problem is: I have big spreadsheet (more than 4500 rows) with a lot of data in the first column - for ex. with types of fruits, which are not unique, like this:
APPLE
BANANA
APRICOTS
APPLE
BLACKCURRANT
APPLE
BANANA
APRICOTS
etc.
What I need - locate each BANANA, to be able to put in cell beside some info, for ex. YES. I tried to loop solution from Locating a cell's position in google sheets using a string or an integer but for sure my code is wrong. I already spent a lot of hours to invent something, but still don't understand what I'm missing.
function test(){
var dispatch = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("FRUITS");
var find = dispatch.getRange("A:A").getValues();
var name = "BANANA";
var lastRow = dispatch.getLastRow();
var n = 1;
var temp = dispatch.getRange(n, 2).getValue();
var i = 0;
while (temp != ""){
for(var n in find){
if(find[n][0] === name){break}
}
n++;
var n = n + i;
dispatch.getRange(n, 2).setValue("YES");
var temp = dispatch.getRange(n, 2).getValues();
var find = dispatch.getRange(n, 2, lastRow).getValues();
var i = n;
}
}
I will be very grateful for the help.
The code example is below:
function test(){
var dispatch = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("FRUITS");
var range = dispatch.getRange(1, 1, dispatch.getLastRow(), 2);
var values = range.getValues();
values.map(function(row) {
if (row[0] == "BANANA")
row[1] = "YES";
});
range.setValues(values);
}
JS array map() method does the most part of work. We convert range values to JS array and back after mapping completes.

How to create priorities list and change them without leaving any gaps

I'm trying to write a simple script for google spreadsheet.
The code should work as follows: it should be a priority list, which can be simply edited. For example I have a list of 7 tasks and want to change the priority of one task. My code simply looks for values greater or eqauls then the value I put in a cell and increments them by 1. So at the end the last task has priority 8 and I have only 7 tasks, so there is one gap somewhere.
Here is what I've already done:
function onEdit(e) {
var range = e.range;
var numRowsWithData = SpreadsheetApp.getActiveSheet().getDataRange().getNumRows();
var columnOfCellEdited = range.getColumn();
var cellEdited = range.getA1Notation();
if (columnOfCellEdited === 2) {
var valueOfEditedCell = range.getValue();
for (var i = 2; i <=numRowsWithData; i++) {
var rangeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
var currentCell = rangeSheet.getCell(i, 2);
var currentValue = currentCell.getValue();
if(currentValue >= valueOfEditedCell) {
if(cellEdited == currentCell.getA1Notation()){
continue;
}
currentCell.setValue(currentValue+1);
};
}
};
};
This script works, but I've got some missing numbers, because every time I add 1 to the current value.
How to edit it to have all the numbers in proper order, without any missing ones?
I know it's not very complicated, but I don't have any idea for now:/
Thanks in advance!
Completely rewritten answer, following clarification of the question.
This function reads all of the existing priorities, including the new entry. Any existing value greater than the new one is incremented by 1. An array is used to store the row on which each value occurs. The array is then looped over, skipping any undefined entries (missing numbers), and renumbering those that do exist starting from 1.
This doesn't change the order of the rows. It simply renumbers the second column.
Where possible I've left your original code intact, and re-used your approach in the new bit.
function onEdit(e) {
var range = e.range;
var numRowsWithData = SpreadsheetApp.getActiveSheet().getDataRange().getNumRows();
var columnOfCellEdited = range.getColumn();
var cellEdited = range.getA1Notation();
if (columnOfCellEdited === 2) {
var valueOfEditedCell = range.getValue();
// get snapshot of current priorities
var priorityRows = [];
for (var row = 2; row <=numRowsWithData; row++) {
var rangeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
var currentCell = rangeSheet.getCell(row, 2);
var currentValue = currentCell.getValue();
if(currentValue >= valueOfEditedCell && cellEdited !== currentCell.getA1Notation()) {
currentValue++;
};
priorityRows[currentValue] = row;
}
// renumber priorities
var newPriority = 1;
for (var i = 0; i < priorityRows.length; i++) {
var row = priorityRows[i];
if (row) {
var rangeSheet = SpreadsheetApp.getActiveSheet().getDataRange();
var currentCell = rangeSheet.getCell(row, 2);
currentCell.setValue(newPriority++);
}
}
}
}

IF statement in a while loop not working, Where am I going wrong?

So am still learning Javascript and trying to find my way.
I'm trying to get something working that will save me hours of time per week at work.
It's a script for a googlespread sheet. It's meant to look for key values in columns and move data accordingly. It should then split the data, by region into an external spreadsheet, into induvidual tabs.
I think I have the split bit right, but where it's doesn't seem to work is decidng what should be moved and what shouldn't be. I'm pretty sure something is wrong with my While loop and possibly my if statement inside the while loop :(
I'd be really grateful if anyone can point out the error of my ways. Here is the full script.
The only items that should be moved are as follows.
IF column AV = Need to remove details AND Column AW is not blank AND column AX is not blank. AND column BC is not blank. Then it should be pushed to archive.
OR
IF AV is blank AND aw is not blank AND bc is not blank. Then that should also be pushed to archive.
Many thanks
function testNinja(){
var sourceSheet = "Change of details requests";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sourceSheet);
var values = sheet.getDataRange().getValues();
var valuesLength = values.length;
var archive = [];
var counter = 1;
var agentSheet = "ID HERE";
// To make things easier, I've defined the worksheet names and the region names into arrays. Position is important, as entry 0 matches entry 0
var regionSheets = ["R/North East&Yorks","R/North West","R/South East","D/South London","D/East","D/North London","M/Midlands","M/Wales&S.West","M/Scotland/N.Ireland"];
var regionNames = ["North East & Yorks","North West","South East","South London","East England","North London","Midlands","Wales & S West","Scotland & N Ireland"];
// giving key columns & key values a var to save typing. Using column letters as var name.
var av = values[counter][47];
var aw = values[counter][48];
var ax = values[counter][49];
var bc = values[counter][54];
var bf = values[counter][57];
var bk = "Need to Remove Details";
var sf = "SET SF TASK";
// put matching values into an archive. This bit doesn't seem to be working correctly
while(counter < valuesLength)
{
if (bf == sf)
{
counter++;
}
if(av == bk && aw != "" && ax != "" && bc != "" || av == "" && aw != "" && bc !="")
{
archive.push(values.splice(counter, 1)[0]);
}
else
{
counter++;
}
}
// create a function called 'move' to move columns into new positions
Array.prototype.move = function (old_index, new_index) {
if (new_index >= this.length) {
var k = new_index - this.length;
while ((k--) + 1) {
this.push(undefined);
}
}
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
}
//go through the archive, use the move function, and trim the end columns. Finally add a timestamp
var timestamp = new Date();
for (var i = 0;i<archive.length;i++)
{
archive[i].move(68,5);
archive[i].length = 73;
archive[i][61] = timestamp;
}
// Split the data by region. There is a region value in Column 65. This determinds what Sheet data is moved too.
var regionData = [];
var regionSs = SpreadsheetApp.openById(agentSheet);
for(var i = 0; i < regionSheets.length;i++){
var regionSheet = regionSs.getSheetByName(regionSheets[i]);
var regionName = regionNames[i];
for(var j = 0; j < archive.length;j++){
var value = archive[j][66];
if(value == regionName){
regionData.push(archive.splice(j, 1)[0]);
}
}
//Write data to sheet here.
var regionDataLength = regionData.length;
if (!regionDataLength) continue;
var lastRow = regionSheet.getLastRow();
var requiredRows = lastRow + regionDataLength - regionSheet.getMaxRows();
if (requiredRows > 0) regionSheet.insertRowsAfter(lastRow, requiredRows);
regionSheet.getRange(lastRow + 1, 1, regionDataLength, regionData[0].length).setValues(regionData);
// clear array for new region data
regionData = [];
// Get data validation from CC2, then apply to CC2:CC .
var CCrule = regionSheet.getRange("CC2").getDataValidation();
regionSheet.getRange("CC2:CC").setDataValidation(CCrule);
}
}
var counter = 1;
//...
var bf = values[counter][57];
//...
var sf = "SET SF TASK";
//...
while(counter < valuesLength)
{
if (bf == sf)
{
counter++;
}
//...
I think the problem is the assignment to the variable bf (and the others). You are assigning values[1][57] to bf, so you are not actually iterating through the columns when the counter is increased (which it never will, if values[1][57] != sf).
So if you wanted to use the column identifiers to save typing, I think you would need to assign values inside the while loop, not before it.
Try with this if:
if ((av == bk && aw != "" && ax != "" && bc != "") || (av == "" && aw != "" && bc !=""))
You must put parentheses around your logic blocks otherwise the computer has no way of knowing how to group the conditions and in this case the result depends on operator precedence.
Edit: I was wrong, in JS && has higher precedence than || so programmatically it doesn't change anything, but however it's a best practice for the clarity and readability of the code to put the parentheses.

Pull data from one Google spreadsheet to another using Google script

I have 2 spreadsheet in my Drive. I want to pull data from a cell in 1 spreadsheet and copy it in another.
The spreadsheet "TestUsage" will sometimes have data in column A, but none is column B.
I would like so that when I open the spreadsheet, it would populate that empty cell in sheet "TestUsage" from sheet "TestParts".
Here is my code:
var ssTestUsage = SpreadsheetApp.getActiveSpreadsheet();
var sTestUsage = ssTestUsage.getActiveSheet();
var lastRowTestUsage = sTestUsage.getLastRow();
var rangeTestUsage = sTestUsage.getSheetValues(1, 1, lastRowTestUsage, 4);
var TESTPARTS_ID = "1NjaFo0Y_MR2uvwit1WuNeRfc7JCOyukaKZhuraWNmKo";
var ssTestParts = SpreadsheetApp.openById(TESTPARTS_ID);
var sTestParts = ssTestParts.getSheets()[0];
var lastRowTestParts = sTestParts.getLastRow();
var rangeTestParts = sTestParts.getSheetValues(1, 1, lastRowTestParts, 3);
function onOpen() {
for (i = 2; i < lastRowTestUsage; i++) {
if (rangeTestUsage[i][0] !== "" && rangeTestUsage[i][1] == "") {
for (j = 1; j < lastRowTestParts; j++) {
if (rangeTestUsage[i][0] == rangeTestParts[j][0]) {
Logger.log(rangeTestUsage[i][1]);
Logger.log(rangeTestParts[j][1]);
rangeTestUsage[i][1] = rangeTestParts[j][1];
break;
}
}
}
}
}
The problem with this is this doesn't do anything:
rangeTestUsage[i][1] = rangeTestParts[j][1];
I know there must be a method that can get data from one range to another.
Please let me know if I am totally incorrect or I am on the right path.
the statement
"this doesn't do anything:"
rangeTestUsage[i][1] = rangeTestParts[j][2];
is not really true... and not really false neither..., actually it does assign the value to rangeTestUsagei but you dont see it because it is not reflected in the spreadsheet.
Both values are taken from the Sheet using getValues so at that time they are both array elements.
What is missing is just writing back the array to the sheet using the symetrical statement setValues()
Give it a try and don't hesitate to come back if something goes wrong.
EDIT :
I didn't notice at first that you were using getSheetValues instead of getValues (simply because I never use this one)... the only difference is that getValues is a method of the range class while yours belongs to the sheet class; the syntax is similar in a way, just use
Sheet.getRange(row,col,width,height).getValues()
it takes one word more but has the advantage to have a direct equivalent to set values
Sheet.getRange(row,col,width,height).setValues()
Serge insas has a good explanation of why your code doesn't work and hints at the solution below.
I recommend you use an array to store the updated values of column B that you want then set the entire column at the end.
Modifying your code...
var ssTestUsage = SpreadsheetApp.getActiveSpreadsheet();
var sTestUsage = ssTestUsage.getActiveSheet();
var lastRowTestUsage = sTestUsage.getLastRow();
var rangeTestUsage = sTestUsage.getSheetValues(1, 1, lastRowTestUsage, 2);
var TESTPARTS_ID = "1NjaFo0Y_MR2uvwit1WuNeRfc7JCOyukaKZhuraWNmKo";
var ssTestParts = SpreadsheetApp.openById(TESTPARTS_ID);
var sTestParts = ssTestParts.getSheets()[0];
var lastRowTestParts = sTestParts.getLastRow();
var rangeTestParts = sTestParts.getSheetValues(1, 1, lastRowTestParts, 2);
var colB = [];
function onOpen() {
for (i = 2; i < lastRowTestUsage; i++) {
if (rangeTestUsage[i][0] !== "" && rangeTestUsage[i][1] == "") {
var matched = false;
for (j = 1; j < lastRowTestParts; j++) {
if (rangeTestUsage[i][0] == rangeTestParts[j][0]) {
//Logger.log(rangeTestUsage[i][1]);
//Logger.log(rangeTestParts[j][1]);
colB.push([rangeTestParts[j][1]]); // push the value we want into colB array
matched = true;
break;
}
}
if(!matched) // this is in case you don't have a match
colB.push([""]); // incase we don't have a matching part
} else {
colB.push([rangeTestUsage[i][0]]); // we already have a value we want so just add that to colB array
}
}
sTestUsage.getRange(2,2,lastRowTestUsage).setValues(colB); // update entire column b with values in colB array
}

Categories