How can I shift rows by one when they match a substring? - google-apps-script

Could someone please help me solve this?
Attached is the example sheet of what I am trying to do:
https://docs.google.com/spreadsheets/d/12w4rGArGi1I1wlpm5yJJtT5AAlMM4LcZC31_DpP6jZQ/edit?usp=sharing
I am trying to shift the rows that contain "PO" in my data selection. (see shift function)
It should change from this:
to this:
I have written a script but it isn't working and I am not getting an error message.
I have a feeling it is because I am trying to "+1" my array to offset my values. Please help!
Here is my current script:
function shift() {
try{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var as = ss.getActiveSheet();
var ar = as.getActiveRange();
var vals = ar.getValues(); // get values from the active (selected) range...... intended use is to draw a selection where the PO BOX substring is in the leftmost column
// SpreadsheetApp.getUi().alert(vals); //for checking values
var r; // variable for rows
for (r = 0; r < vals.length; r++){ // for each row, up to the last row (iterate over all rows from top to bottom)
if(vals[r][0].indexOf("PO") != -1){ // if first column in each row contains "PO"
SpreadsheetApp.getUi().alert("found a PO BOX"); // make annoucement
var c; // variable for columns
var cols = []; // array for column data (for debugging)
for (c = 0; c < vals[r].length; c++){ // for each columns in row (iterating over each cell in row from left to right)
cols[c] = vals[r][c]; // add the current cell value to an array
vals[r][c+1] = vals[r][c]; // take the value from the current cell and assign it to the next cell (+1 to the column)
}
SpreadsheetApp.getUi().alert(cols); // show me the data that cas changed
}
}
ar.setValues(vals); // set new values to active range
}
catch(err){
SpreadsheetApp.getUi().alert(err);
}
}
Expectations:
get the data range (my intended testing range is B:1 to D:12)
iterate through each row, and on each row, iterate through each
cell(column)
If the very first cell in the current row(vals[r][0]) contains the
substring "PO" , then I want to change the values of that row such
that they all shift over by one column, and leave the very first
cell as a blank string
I change the values by replacing the current values with the same
values BUT +1 to the columns row(vals[r][c]) = row(vals[r][c+1])
Realit(EDIT)y:
Exceeded memory limit error... possible infinite loop happening... i THINK it might be because I am adding a column that does not exist in the data range, but how would I get around this problem?
SOLVED!!!!
After much trial and error, I cane up with an answer (posted int he answers)
feels great to solve my own problem for the first time ever!

my problem was exactly what i expected. I was trying to assign values outside of the range.
The way that I solved this was to assign all of my values to an array(I also shifted them over in the process), and then loop through the range(the range is just a two dimensional array), assigning all of the shifted values to the range columns.
function shift() {
try{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var as = ss.getActiveSheet();
var ar = as.getActiveRange();
var vals = ar.getValues();
var r; //variable for rows
for (r = 0; r < vals.length; r++){ // for each row, up to the last row (iterate over all rows from top to bottom)
if((vals[r][0].indexOf("PO") != -1)||(vals[r][0].indexOf("P0") != -1)){ // if first column in each row contains "PO"
var c; // variable for columns
var cols = []; // array to store all data temporarily (will be uses to set new values later)
for (c = 0; c < vals[r].length; c++){ // then iterate over each column(cell) in the row
if(c == 0){ // if it is the first row,
cols[c+1] = vals[r][c]; // assign second index of the array with the PO value (to simulate a shift)
cols[c] = ""; // assign the first index of the array a blank string
}
else{ // if it is not the first row
cols[c+1] = vals[r][c]; // assign each additional column value to the next index (+1) of the array
}
}
for (c = 0; c < vals[r].length; c++){ // once the array is finished, loop through the columns again foreach row
vals[r][c] = cols[c]; // this time, assigning the new values to the corresponding array indices
}
}
}
ar.setValues(vals); // now, set the values that you reassinged to the array
}
catch(err){
SpreadsheetApp.getUi().alert(err);
}
}

Related

Dependent Data Validation in Google Apps Script

I need to add Data Validation to a range of cells based on the State chosen in the previous cell.
I've run this code which works ok for a limited amount of data but is not working on the actual spreadsheet.
function onEdit(e) { // Runs automatically when the user edits the sheet
var value = e.value; // Get the new value entered into the edited cell
var col = e.range.getColumn(); // Get the column number of the edited cell
var row = e.range.getRow(); // Get the row number of the edited cell
if (col == 6 && row >= 10 && row <= 854) { // Make sure that the edited cell is part of the table
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Hoja 1');
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Localidades'); // Get the sheet that has the table with the list of cities
var dropdownData = sheet2.getRange("A1:X594").getValues(); // Get the table with the list of cities.
var listOfCountries = dropdownData[0]; // The top row is the list of countries
var countryColumn = listOfCountries.indexOf(value); // Find the column in which the country name appears
if (countryColumn != -1) { // If the new country name is in the list
var cityList = [];
for (var dataRow = 1; dataRow < dropdownData.length; dataRow++) {
cityList.push(dropdownData[dataRow][countryColumn]);
}
var cityCell = sheet.getRange(row, col + 1);
cityCell
.clearDataValidations() // Remove any existing data validation in the target cell
.clearContent(); // Clear the cell
var rule = SpreadsheetApp.newDataValidation().requireValueInList(cityList, true).build();
cityCell.setDataValidation(rule);
}
}
}
I've debugged and it gets the CityList all right so don't know what's the problem really. Any help??
There is a limited amount of allowed dropdown options
Through testing you can easily verify that it is 500 options.
So, if you hardcode "A1:X594" you are above the limit.
However, for most of the provinces in your data the amount of options is less than 594 and your array contains many empty values.
You can remove all duplicates inlcuding emoty values by filtering, e.g.
cityList = cityList.filter(function (value, index, self) {
return self.indexOf(value) === index;
})
As for the provinces where you have many entries (e.g. Buenos Aires), maybe you can try to subdivide it into smaller regions so you have less than 500 dropdown options per dropdown?

Receiving error The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues [duplicate]

I am getting this error:
"The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues."
in my Google Apps Script when I try to write an array of values to a sheet.
Below is a shortened (simplified) version of code. The actual code runs through about 10,000 records.
The error is generated in the last line, when the setValues is called.
I know I'm missing something super simple here.
function writeArrayToSheet() {
var ss = SpreadsheetApp.openById("Spreadsheet_ID");
var orderSheet = ss.getSheetByName("Sheet_Name");
var vTable = orderSheet.getRange(1,6,5,11).getValues(); //Raw data
var vWriteTable = []; //Data that will be written to sheet
var updateTime = new Date();
var i = 0;
var vSeconds = 0;
while (i < 5 && vTable[i][0] != "") {
//Logic section that calculated the number of seconds between
if (vSeconds == 0) {
vWriteTable.push("");
} else {
if (vTable[i][6] < certain logic) {
vWriteTable.push("Yes");
} else {
vWriteTable.push("");
}
}
i = i + 1;
} // End while
orderSheet.getRange(1,20,vWriteTable.length,1).setValues(vWriteTable);
} //End Function
This is what vWriteTable looks like when debugging:
setValues accepts(and getValues() returns):
1 argument of type:
Object[][] a two dimensional array of objects
It does NOT accept a 1 dimensional array. A range is always two dimensional, regardless of the range height or width or both.
If A1:A2 is the range, then corresponding values array would be like:
[[1],[3]]
Similarly, A1:B1 would be
[[1,2]]
A1:B2 would be
[[1,2],[3,4]]
Notice how the two dimension provides direction and that it is always a 2D array, even if the height or width of the range is just 1.
Solution:
Push a 1D array to make the output array 2D.
Snippet:
vWriteTable.push(/*Added []*/["Yes"]);
More information:
For a more detailed explanation of arrays in google sheets, checkout my answer here.
getValues returns object[rows][columns]
1 row object [[1...n]] - this maybe confusing b/c it may not look like 2D but it is 2D object[1 row][n columns]
2 row object [[1...n],[1...n]] - object[2 rows][n columns]
etc [[1...n],...,[1...n]] - etc
If you need to setValues to rectangular range with n rows and m columns, create EXACTLY n by m array 1st, then fill it with your values and send it to the MATCHING range in your spreadsheet. Here is some of my code that might help.
Getting values from 1st m cells in n-th row and pushing them in simple 1D array:
var arr = [];
var dataRow = sheet.getRange(n,1,1,m).getValues();
for (var j=0; j<dataRow.length; j++){
arr.push(dataRow[0][j]);
}
to replace them with new values from 1D array arr:
//create 2D array with just 1 row and m columns
var dataRow = new Array(1);
dataRow[0] = new Array(arr.length);
//fill it with values
for(var j=0;j<arr.length;j++){
dataRow[0][j]=arr[j];
}
//send it to the spreadsheet
sheet.getRange(n,1,1,arr.length).setValues(dataRow);

Apps Script, difficulty on trigger action when 2 values match [duplicate]

I am getting this error:
"The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues."
in my Google Apps Script when I try to write an array of values to a sheet.
Below is a shortened (simplified) version of code. The actual code runs through about 10,000 records.
The error is generated in the last line, when the setValues is called.
I know I'm missing something super simple here.
function writeArrayToSheet() {
var ss = SpreadsheetApp.openById("Spreadsheet_ID");
var orderSheet = ss.getSheetByName("Sheet_Name");
var vTable = orderSheet.getRange(1,6,5,11).getValues(); //Raw data
var vWriteTable = []; //Data that will be written to sheet
var updateTime = new Date();
var i = 0;
var vSeconds = 0;
while (i < 5 && vTable[i][0] != "") {
//Logic section that calculated the number of seconds between
if (vSeconds == 0) {
vWriteTable.push("");
} else {
if (vTable[i][6] < certain logic) {
vWriteTable.push("Yes");
} else {
vWriteTable.push("");
}
}
i = i + 1;
} // End while
orderSheet.getRange(1,20,vWriteTable.length,1).setValues(vWriteTable);
} //End Function
This is what vWriteTable looks like when debugging:
setValues accepts(and getValues() returns):
1 argument of type:
Object[][] a two dimensional array of objects
It does NOT accept a 1 dimensional array. A range is always two dimensional, regardless of the range height or width or both.
If A1:A2 is the range, then corresponding values array would be like:
[[1],[3]]
Similarly, A1:B1 would be
[[1,2]]
A1:B2 would be
[[1,2],[3,4]]
Notice how the two dimension provides direction and that it is always a 2D array, even if the height or width of the range is just 1.
Solution:
Push a 1D array to make the output array 2D.
Snippet:
vWriteTable.push(/*Added []*/["Yes"]);
More information:
For a more detailed explanation of arrays in google sheets, checkout my answer here.
getValues returns object[rows][columns]
1 row object [[1...n]] - this maybe confusing b/c it may not look like 2D but it is 2D object[1 row][n columns]
2 row object [[1...n],[1...n]] - object[2 rows][n columns]
etc [[1...n],...,[1...n]] - etc
If you need to setValues to rectangular range with n rows and m columns, create EXACTLY n by m array 1st, then fill it with your values and send it to the MATCHING range in your spreadsheet. Here is some of my code that might help.
Getting values from 1st m cells in n-th row and pushing them in simple 1D array:
var arr = [];
var dataRow = sheet.getRange(n,1,1,m).getValues();
for (var j=0; j<dataRow.length; j++){
arr.push(dataRow[0][j]);
}
to replace them with new values from 1D array arr:
//create 2D array with just 1 row and m columns
var dataRow = new Array(1);
dataRow[0] = new Array(arr.length);
//fill it with values
for(var j=0;j<arr.length;j++){
dataRow[0][j]=arr[j];
}
//send it to the spreadsheet
sheet.getRange(n,1,1,arr.length).setValues(dataRow);

Google apps scripting, count streak

Column A has a timestamp in ascending order. Column G has employee names. Each cell in Column L has either a 1, a 0, or is blank. I'm attempting to calculate the latest streak of 1's in Column L, per employee. My current attempts have involved a list of employee names with the following filter, "filter L:L where G:G = employee name and L:L is not blank". My thought was to nest this filter inside a custom formula that iterates through the filtered results, counting the streak of 1s and stopping at the first 0, returning the count of ones. Since the timestamps are in ascending order, I would need it to iterate from the last row up (or figure out how to change my data imports to append to the top of the sheet instead of the bottom). I have very little programming experience, but here was my failed attempt. How incredibly far off am I? (btw, I only just realized I need it to iterate from bottom up):
function streak() {
var sheet = SpreadsheetApp.getActiveSheet()
var range = sheet.getActiveRange();
var cell = sheet.getActiveCell();
var value = cell.getValue();
for (i in range) {
var count = 0
if (value = 1) {
count += 1;
} <br>
return count;
} <br>
} <br>
You are sort of close but depending on what you want to do, this could end up being quite complex. Nevertheless, you are on the right track.
What you want to do first is get the values for the active range on your spreadsheet.
var myDataVals = SpreadsheetApp.getActiveSheet().getActiveRange().getValues();
This will assign a 2-dimensional array to myDataVals.
From here you can loop through the outer array (row by row) and then the inner array within each (column by column). There are many ways to solve this question and it really depends on exactly what you're trying to do. You can learn more about basic JavaScript programming here: https://www.javascript.com/resources
I wrote a sample function that you can play around with. My solution was to iterate through the rows (outer array) and then assign them to an object where the keys are the employee names; this effectively sorts the rows by name. Then I sorted the rows within each name by timestamp value in descending order. Then, starting from the first (most recent) timestamp, I check to see if column L has a 1 or a 0. If I find a 1, I increment the number located in the streaks object at the key with the name by 1. If I find a 0, the streak is broken and I exit the while loop by changing my streakEnded boolean to true. If the cell is blank or the value is anything other than a 1 or 0, no action is taken and the loop proceeds until it is stopped or there are no more rows remaining.
Finally, the streaks object is returned. From there you could make a new page in the sheet, or send the results in an email, or anything else you may want to do. For now, I just logged the object to the script logger. You can see the results by choosing (View > Logs) in the script editor. Make sure you've highlighted the range of cells!
function streak() {
var activeRangeVals, allStreaks, columns;
// Get the active range as a 2-dimensional array of values
activeRangeVals = SpreadsheetApp.getActiveSheet().getActiveRange().getValues();
// Define which columns contain what data in the active Range
// There are better ways to do this (named ranges)
// but this was quickest for me.
columns = {
'name': 6, // G
'streak': 11, // L
'time': 0 // A
};
allStreaks = getStreaks(getEmployeeRowsObject(activeRangeVals));
Logger.log(allStreaks);
return allStreaks;
function getEmployeeRowsObject(data) {
// Create an empty object to hold employee data
var activeName, activeRow, employees = {}, i = 0;
// Iterate through the data by row and assign rows to employee object
// using employee names as the key
for(i = 0; i < data.length; i+= 1) {
// Assign the active row by copying from the array
activeRow = data[i].slice();
// Assign the active name based on info provided in the columns object
activeName = activeRow[columns['name']];
// If the employee is already defined on the object
if(employees[activeName] !== undefined) {
// Add the row to the stack
employees[activeName].push(activeRow);
// If not:
} else {
// Define a new array with the first row being the active row
employees[activeName] = [activeRow];
}
}
return employees;
}
function getStreaks(employees) {
var activeRow, activeStreak, i = 0, streaks = {}, streakEnded;
for(employee in employees) {
activeRow = employees[employee];
// Sort by timestamp in descending order (most recent first)
activeRow = activeRow.sort(function(a, b) {
return b[columns['time']].getTime() - a[columns['time']].getTime();
});
i = 0, streaks[employee] = 0, streakEnded = false;
while(i < activeRow.length && streakEnded === false) {
activeStreak = parseInt(activeRow[i][columns['streak']]);
if(activeStreak === 1) {
streaks[employee] += 1;
} else if(activeStreak === 0) {
streakEnded = true;
}
i += 1;
}
}
return streaks;
}
}

2D array reading issue?

I want to create a sort of "stack" and every time I delete an item, the sheet removes the blank cells. I can't use a filter function for this, obviously.
I am having trouble reading the array that is created for this purpose.
My very pseudo-code : I create an empty array, get all the values (including the empty ones), populate my array with all the values except the empty ones, and finally clear the stack and set the values with my array.
Here is my code :
function updateStack() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("main");
var zone = sheet.getRange(1, 1, 1, 10);
//seems that .getValues() returns a 2d array
var values = zone.getValues();
var j = 0;
var data = new Array();
for (var i = 0 ; i < 10 ; i++) {
//the problem seems to be here : I can't access the 2d array. After reading the debugging console about 1000 thousand times
// I discovered the 2 pairs of []
//I've found multiple ways to detect empty cells. Not sure if this is the right one. I've tried the .length = 0 trick, but something
// was wrong, maybe because of the "2dimensionality"
if (values[i] != "") {
data[j] = values[i];
j = j++;
} else {
// do nothing if the cell contains nothing
}
//not sure if I have to use return ! Don't know where to put it exactly too...
return data;
zone.clear();
//length of range must be the same as the array's length
zone = sheet.getRange(1, 1, 1, data.length);
zone.setValues(data);
}
}
There are many comments in my code, I hope you will understand.
A link to my test sheet : http://bit.ly/1JiWutn
Thanks for any help !
Currently, you have a section of code like this:
if (values[i] != "") {
data[j] = values[i];
j = j++;
} else {
You are testing for an empty string:
values[i] != ""
But values[i] is an inner array. Your code is getting only one row, and 10 columns.
var zone = sheet.getRange(1, 1, 1, 10);
So, the array looks like this:
[ [cell one,cell two,cell three,etc,cell ten ] ]
values[i] returns an inner array, not a value.
To get the cell value use:
if (values[0][i] != "") {
You need two indexes, the first index will always be zero. There is only one inner array with all the cell values in it.
Next, use push to add a value to the data array:
data.push(values[0][i]);
Another issue is where you have the return statement. A return statement kills the current function. Anything after the return statement inside of that function will not run. So, you can't have a return statement where you have it, and get the code to write values to the spreadsheet. You can do both. You can both write values to the sheet, and return something, but put the return at the end. The return, returns something to whatever function called this function.
To set values, the values MUST be in a two dimensional array. Your data array is not a 2D array. You must add the data array to yet another array.
var my2Darray = [];
my2Darray.push(data);
zone = sheet.getRange(1, 1, 1, data.length);
zone.setValues(my2Darray);
If you are only testing for blank cells across a whole row then the test you have is almost usable. If you concatenate all the values in that array then the resultant can be compared to "".
// join all the values with nothing between them
// compare the result to empty string
if(values[i].join("") !== "") {
// if at least one cell contained something
data.push(values[i]); // Stackius Popularious
}