Pass values in array to a range of cells on Google Spreadsheet - arrays

I'm tryin to read in a column of cells its numbers and backgrounng colors ,and copy it to another columuns.
I can read both and put values in arrays, so what I need is pass the values in these arrays to the range that should receive it.
That's what I'm trying, reading values in column A and pass it to column C:
var origin =["A1:A5"]
var destiny=["C1:C5"]
var ColCell = SpreadsheetApp.getActiveSpreadsheet().getRange(origin).getBackgrounds();
var ValCell=SpreadsheetApp.getActiveSheet().getRange(origin).getValues();
// now i have two arrays, ColCell and ValCell,
// that have backgound colors and Values of original
// cells in the range "A1:A5".
I can't figure how to pass values in ColCell and ValCell to ["C1:C5"]. Have anyone had this problem before?

You want to copy the values (background colors and cell values) of "A1:A5" of the active sheet to the range of "C1:C5" of the active sheet using Google Apps Script.
If my understanding is correct, how about this modification?
Modified script:
var origin = ["A1:A5"];
var destiny = ["C1:C5"];
// Retrieve values.
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange(origin);
var ColCell = range.getBackgrounds();
var ValCell = range.getValues();
// Set values.
sheet.getRange(destiny).setBackgrounds(ColCell).setValues(ValCell);
References:
setBackgrounds(color)
setValues(values)
If I misunderstood your question and this was not the result you want, I apologize.

Related

Problem with copying volatile data values to rows of a google sheet

I am trying to convert an old Excel VBA program to Google Sheets for projection 30 years of financial results. The key issue is the VBA Excel sheet haw a row (a1:ad1) of (30) cells that each contain the volatile function Rand() to generate a random number in each cell through each of 1000 loops:
In each loop these cells 1) reset their random number, then 2) some rows of calculations are made based on the new random numbers, then 3) then a resulting row of 30 new values in copy-pasted (values only) to another part of the sheet so the individual results can be statistically analyzed.
The copy-paste in google sheets is too slow and I hit a 6 minute execution time out, so I am trying to accumulate each loop into a single 2D array, each row being one of the 1000 loop results in 3 above,then write the entire array back to the sheet at once. To get the 30 cells to recalc, each time I read a row, I force a new value to be set in a single cell of the spreadsheet at the beginning of each loop. That seems to work, but trying move the data from the row to the accumulation array and then writing the values back to individual rows in the sheet at once, I always wind up with arrays the have too many dimensions some other problems getting the data back to the sheet as a 1000 x 30 range of static data.
Here's the basic outline of where I am so far after many variations of this :
function test1(){
var sheet=SpreadsheetApp.getActive().getSheetByName('UST_Results'),
range;
var values=[];
var LargeArray= Array(new Array()) //new Array()
for (var i=0;i<3;i++) { // using a small number (3) rows to get it work
values =getRandRow(sheet)[0][i];
LargeArray(LargeArray.length) = JSON.stringify(values)
};
};
function getRandRow(sheet) {
var values_array =[];
let myData =[];
var cell = 0;
// force the Rand() to recalc before getting the next copy of A1:Ad1
for (var i=0;i<1;i++) {
var cell = sheet.getRange(i+5,1);
cell.setValue(i);
SpreadsheetApp.flush();
}
range = sheet.getRange('A1:ad1');
values_array = range.getValues();
myData.push(values_array);
return myData;
}
A picture of the sheet testing with just the rand numbers and not the derived row.
enter image description here

Filter a 2D array using another 1D array

I am trying to use arrays to clean up a list of package tracking updates. The data is organised in 3 columns:
| Tracking No | Status | Detail |
The scripts first reads all the data from the sheet, then derives a list of tracking numbers which have been delivered. It then needs to use this array to remove all instances of this tracking number from the original array to create a new array. Finally, both arrays are written to 2 different sheet.
The code ran perfectly a few times, but now is failing now. Any help to find the fault is appreciated. The 1st two arrays are created without any issues, but the 3rd array is not created as expected, the tracking numbers which exist in the 2nd array are not being removed.
I am new to scripting, and most of this code is copied from the internet, so any other tips are welcome as well! I know it can be done by looping, but the dataset is large (15000 lines), and arrays seem to work faster than looping.
function identifydelivered2() {
spreadsheet = SpreadsheetApp.getActive();
sheet = spreadsheet.getSheetByName("Sheet1");
targetsheet = spreadsheet.getSheetByName('Delivered')
//find last row in delivered sheet
var targlrow = targetsheet.getLastRow() + 1;
//find last row in Sheet1
var lastrow = sheet.getLastRow();
var capturerange = [[]];
//read all values from Sheet1
capturerange = sheet.getRange(2, 1, lastrow, 3).getValues();
//stores number of rows captured
var clearlen = capturerange.length
//creates a filtered 2D array of all the delivered rows
let delivered = capturerange.filter(dataRow => dataRow[1] === 'Delivered' || dataRow[1] === 'delivered');
//creates a 1D matrix of all tracking numbers which have been delivered
let deliverednos = delivered.map(x => x[0]);
//creates a filtered 2D array of numbers not in deliverednos array
let intransit = capturerange.filter(dataRow => dataRow[0] != deliverednos);
try{
//write delivered rows to new sheet
targetsheet.getRange(targlrow,1,delivered.length,3).setValues(delivered);
//clear contents of main sheet
sheet.getRange(2,1,clearlen,3).clearContent();
//write in-transit rows to sheet1
sheet.getRange(2,1,intransit.length,3).setValues(intransit);
}
catch(e){
Browser.msgBox('No delivered packages found!')
}
}
Use Set to create a set of deliverednos.
const deliverednos = new Set(delivered.map(x => x[0]));
//creates a filtered 2D array of numbers not in deliverednos array
const intransit = capturerange.filter(dataRow => !deliverednos.has(dataRow[0]));

Copy and Pasting a Moving Range in Google Sheets

I'm very much still learning the basics of script writing, but I have a rather specific question that I've not been able to find a solution for - perhaps because I might be phrasing it wrong.
Essentially, In sheet MS, I have a range of data (A2:O23) with formulas that I want to stay fixed (as in, always reference the same range in a different sheet).
My aim is, at the run of a script, that this block of formulas will copy and paste from the next empty row (in the same columns [A:O]) and that the previous block (A2:O23) will copy and paste over itself as values.
Then, each time the script runs, it does the same thing, copying the latest block of 22 rows beneath itself and pasting itself as values. Essentially having the effect of building a long list of data with the latest (read lowest) range being the formulas and all above being values.
For example, the results of the first time it runs should be that, A24:O45now show the same formula and that A2:O23 are showing as values only. The next time, A24:O45 would be frozen as values and A46:O67 are now the formulas.
I'm not sure if this requires the use of loops or there is a different way to achieve the effect, but the former is something I've not used and the latter is still a mystery to me.
I realise that this might not be especially clear, but I'm happy to field any questions and greatly appreciate any efforts made to help.
Thanks
If I understand you correctly, you want to:
Copy the last 22 rows of your sheet (columns A to O) to the next 22 rows (formulas included).
Copy/paste to the original range as values only.
If the above is correct, then you can copy and run the following code to the script bound to your spreadsheet (see inline comments for detail on what the code is doing, step by step):
function copyPasteValues() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("MS"); // Name of your sheet (change if that's necessary)
var lastRow = sheet.getLastRow();
var firstCol = 1; // First column to copy
var numCols = 15; // Number of columns to copy
var numRows = 22; // Number of rows to copy
if (lastRow > numRows) { // Check if the sheet has at least 23 rows with content (otherwise it would give error when copying the range)
var originRange = sheet.getRange(lastRow - numRows + 1, firstCol, numRows, numCols);
var destRange = sheet.getRange(lastRow + 1, firstCol, numRows, numCols);
originRange.copyTo(destRange); // Copying with formulas to next 22 rows
originRange.copyTo(originRange, {contentsOnly:true}); // Copying only values to same range
}
}
The main method to notice here is copyTo, which can be used to copy the data as it is, with formulas included, or values only, by setting the parameter {contentsOnly:true} (copyValuesToRange could be used too).
I hope this is of any help.
function copypastepaste() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rows=sh.getLastRow();
if(rows>22) {
var rg=sh.getRange(sh.getLastRow()-21,1,22,16);
var vA=rg.getDisplayValues();
var fA=rg.getFormulas();
var nrg=sh.getRange(sh.getLastRow()+1,1,22,16);
nrg.setFormulas(fA);
rg.setValues(vA);
}
}

Most efficient way to get a column range into a 1D array

I have a Google form that collects responses in a linked spreadsheet. One of the fields in the form is Username (column B in the sheet). I'm writing a script to notify me when a new user (unique username) makes a submission. To do this, I:
Get all the values in column B except the latest one
Get the latest username
Check if the latest username is in the list of values (do something if not).
Here's what I've got so far:
function isUserNew() {
var spreadsheet = SpreadsheetApp.openById("INSERT ID HERE");
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheets()[0]);
var last_row_number = sheet.getLastRow()
var penultimate_row_number = (last_row_number-1)
var latest_username = sheet.getRange(last_row_number, 2).getValue()
var usernames = sheet.getRange("B2:B" + penultimate_row_number).getValues();
}
This gives me a 2D array usernames that looks like [[UserA],[UserB],[UserC],[UserC],[UserA],[UserX]]. Then I tried if (usernames.indexOf(latest_username) == -1 but this doesn't work - it only works for a 1D array. I thought of converting the array to 1D using a loop:
for (var i = 0; i < usernames.length; i++) {
Logger.log(usernames[i][0]);
}
This makes the logger correctly log all the usernames I want, but I don't know how to put them into an array.
2 main questions:
How do I get the values into an array instead of the Logger?
Is this the best way to do what I'm trying to do? It seems unnecessary that every time there's a submission, I grab a 2D array, convert to 1D, and compare. The column data is not going to change, it only grows by 1 every time. How will this impact run time as the data grows into the 1000s?
Yep, you could flatten the array and compare with indexOf as you're doing:
var flattened_usernames = [] ;
for (var i = 0; i < usernames.length; i++) {
flattened_usernames.push(usernames[i][0]);
}
But you're better off just doing the check inside the loop:
if (usernames[i][0] === latest_username)
By the way, I assume you know how to grab the username directly from the onFormSubmit event rather than grabbing it from the last row of the sheet. But if not you should learn how to do that!
Instead of:
var usernames = sheet.getRange("B2:B" + penultimate_row_number).getValues();
Do:
var usernames = [].concat(...sheet.getRange("B2:B" + penultimate_row_number).getValues());
This flattens the 2d result of a column into a 1d array. If it's not the shortest way it should be close. I don't know about the efficiency.

Google Spreadsheets replace cell range with data minus empty cells

I am making a script allows you to consolidate a todo list after finished items are erased.
I am getting a range, using an if statement to push all non-zero cell values from the specified range to an array, clearing the cell range and then re-pasting the new array (minus the cells with no data) back into the same range. Here is my code: (thanks mogsdad!)
function onOpen()
{
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [
{name: 'Consolidate To-Do list', functionName: 'consolidatetodolist_'}
];
spreadsheet.addMenu('LB functions', menuItems);
};
function consolidatetodolist_()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("A2:A19");
var rangeValues = range.getValues();
var todosArray = new Array();
for (var i = 0 ; i < rangeValues.length ; i++ ) {
var row = rangeValues[i];
var cell = row [0];
if (null !== cell && "" !== cell) {
todosArray.push(row);
};
};
Logger.log(todosArray);
range.clearContent();
range.offset(0,0,todosArray.length).setValues(todosArray);
};
Your edit trigger should not be manipulating the menu. You should have an onOpen trigger do that. If you want the consolidation to be automatic, then you could drive THAT from your onEdit.
In your consolidatetodolist_() function, you've got a handful of errors regarding value assignment, Javascript fundamentals.
Using the SpreadsheetApp services to get the value of valueInstance isn't necessary. You've already got it, in rangeValues[i][0]. Note the two indexes; the data stored in rangeValues is a two-dimensional array, even though your range was one column. The first index is rows, the second is columns. (Opposite order from A1Notation.)
if ( range.getLength(valueInstance) !== 0 ) should crash, because range is an instance of Class Range which has no getLength() method. What you intend to check is whether the current cell is blank. Since spreadsheet data can be of three javascript types (String, Number, Date), you need a comparison that can cope with any of them, as well as an empty cell which is null. Try if (null !== cell && "" !== cell)...
When you get to setValues, you will need to supply a two-dimensional array that is the same dimensions as range. First, you need to ensure todosArray is an array of arrays (array of rows, where a row is an array of cells). Easiest way to do that here is to use var row = rangeValues[i]; var cell = row[0];, then later todosArray.push(row);. Since you are clearing the original range first, you could then define a new range relative to it using offset(0,0,todosArray.length).setValues(todosArray).
Other nitpicking:
You've got the idea of using meaningful variable names, good. But instead of valueInstance, cell would make this code clearer.
Use of "magic numbers" should be avoided, because it makes it harder to understand, maintain, and reuse code. Instead of i < 18, for example, you could use i < rangeValues .length. That way, if you modified the size of the range you're consolidating, the for loop would adapt without change.
Declaring var i outside of the for loop. No error with what you've done (although a declaration with no value assigned is bad), and it makes no difference to the machine, but for humans it's clearer to define i in the for loop, unless you need to use it outside of the loop. for (var i = 0; ...

Resources