How to use .map() method to place values from one page to anther using data as coordinates for the next - javascript

I am trying to take a data range of X and Y values and place a third column's value into another spreadsheet using the X and Y values as a cell range. How can use .map() method to utilize the array for a series of tasks? Is my only real option to use a "for loop" which can be very slow to go through many rows of data?
I have tried and successfully utilized the "for loop" function to go one by one through one sheet find the values in each column and plot the third column of values in another sheet. However, the larger that list gets the slower the process
function testMap() {
//Open Active Spreadsheet App
var ss = SpreadsheetApp.getActive();
//Capture sheets by name
var sheetCoord = ss.getSheetByName('Coordinates');
var sheetPlots = ss.getSheetByName('Plots');
//Create last row and column variables
var lR = sheetCoord.getLastRow();
var lC = sheetCoord.getLastColumn();
lR = lR-1;
//Create array of all data
var data = sheetCoord.getRange(2, 1, lR, lC).getValues();
//Create variables for placement into proper sheet
var x = data.map(function(f){ return f[0] });
var y = data.map(function(f){ return f[1] });
var v = data.map(function(f){ return f[2] });
sheetPlots.getRange(y, x, lR, lC).setValues(v);
}
I expect this to map the values in the range than incrementally take each value of each range and plot the "v" cell value in the coordinates on the Plots sheet. It just says
"Cannot convert 6,2,1 to (class). (line 23, file "Code")".

You want to put values to the cells using the coordinates retrieved from the Spreadsheet.
Column "A", "B" and "C" of the sheet of Coordinates are the column number, the row number and the value, respectively.
If my understanding is correct, how about this answer?
Issues:
The issue of your script is to use row and column of getRange(row, column, numRows, numColumns) as the array. The official document of getRange(row, column, numRows, numColumns) says as follows.
row: Integer
The starting row index of the range; row indexing starts with 1.
column: Integer
The starting column index of the range; column indexing starts with 1.
numRows: Integer
The number of rows to return.
numColumns: Integer
The number of columns to return.
In your case, it seems that the values are required to be put to the cell of each coordinate. By this, when the data is large, the process cost will become high.
Unfortunately, when setValues of Spreadsheet service is used, values are put to the continuous coordinates. This cannot be used for the situation that the values are put to the discrete coordinates.
For example, if your goal can use the situation that the cells are overwritten by the values including the empty values, setValues() can be used.
Solution:
In order to resolve above issues, I would like to propose to use the method of batchUpdate of Sheets API. When Sheets API is used, the values can be put to the cells of the discrete coordinates by one API call. And from my experiment, for putting values, when the data is large, Sheets API is faster than Spreadsheet service. From this situation, I proposed to use Sheets API.
Modified script:
Before you use this script, please enable Sheets API at Advanced Google Services.
function testMap() {
var ss = SpreadsheetApp.getActive();
var sheetCoord = ss.getSheetByName('Coordinates');
var sheetPlots = ss.getSheetByName('Plots');
var lR = sheetCoord.getLastRow();
var lC = sheetCoord.getLastColumn();
lR = lR-1;
var data = sheetCoord.getRange(2, 1, lR, lC).getValues();
// I modified below script.
var sheetId = sheetPlots.getSheetId();
var requests = data.map(function(e) {
var obj = {};
if (typeof e[2] == "string") obj.stringValue = e[2];
if (typeof e[2] == "number") obj.numberValue = e[2];
return {updateCells: {
range: {sheetId: sheetId, startRowIndex: e[1] - 1, endRowIndex: e[1], startColumnIndex: e[0] - 1, endColumnIndex: e[0]},
rows: [{values: [{userEnteredValue: obj}]}],
fields: "userEnteredValue"
}};
});
Sheets.Spreadsheets.batchUpdate({requests: requests}, ss.getId());
}
References:
getRange(row, column, numRows, numColumns)
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
Advanced Google services
Method: spreadsheets.batchUpdate
UpdateCellsRequest
If I misunderstood your question and this was not the result you want, I apologize. At that time, can you provide a sample Spreadsheet? By this, I would like to confirm your situation.

Regarding the error
y and x are Arrays but the getRange method that uses four arguments require that each one of them are numbers, more specifically, integers. In other words, your code is passing the wrong data type for the first two arguments Ref. https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangerow-column-numrows-numcolumns
It's worth to note that v also is an Array (1D Array) but setValues requires a 2D Array.
Regarding the use of Array.protoype.map
In a broad sense you are using it correctly. Ref. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map, but not for the result that you are looking to get.
There are several ways to achieve that result
Use "brute force" to set the cell values of each coordinate one at a time, perhaps by using a for statement, Array.prototype.forEach or other similar methods.
Use "pseudo brute force", use Class RangeList
Set all the values at once by creating a 2D Array and set all the values at once by using setValues

Related

Google Sheets Apps Script Copy Row of Data If date same as tab name

I found some code that almost does what i need and have tried playing around with it to get it to work, but no luck. I get an export with data with dates in the last column on every row.
I simply want to copy the last column rows of dates to the tabs with the same name.
function MoveDate_FourthDEC() {
var ss=SpreadsheetApp.getActive();
var sh1=ss.getSheetByName("Import");
var sh2=ss.getSheetByName("4/12/2020");
var rg1=sh1.getRange(2,1,sh1.getLastRow(),32);//starting at column2
var data=rg1.getValues();
for(var i=0;i<data.length;i++) {
// 13 = collected should be in this column which is column N
if(data[i][31]=="4/12/2020") {
sh2.appendRow(data[i]);
}}}
Explanation:
Your goal is to copy all the rows for which column AF matches the names of the sheets.
To begin with, you can use forEach() to iterate over every sheet. For each sheet, you want to check whether the sheet name matches a date in column AF. If it does, then you need to filter only the rows that contain this date in column AF and store them into an temporary array:
let temp_data = data.filter(r=>r[31]==sh.getName());
Then you can efficiently copy and paste all the relevant rows to the matching sheet:
sh.getRange(sh.getLastRow()+1,1,temp_data.length,temp_data[0].length).setValues(temp_data);
Side notes:
When dealing with date objects you need to consider the display values in the sheet. This is why I am using getDisplayValues instead of getValues.
Since data starts from the second row, you need to deduct one row from the last row with content, to get the correct range:
getRange(2,1,sh1.getLastRow()-1,32)
I am using includes to check if the sheet name matches the last column. In order to use includes you need to flatten the 2D array that is returned by the getDisplayValues function.
Solution:
function MoveDate_FourthDEC() {
const ss = SpreadsheetApp.getActive();
const sh1 = ss.getSheetByName("Import");
const shs = ss.getSheets();
const dts = sh1.getRange('AF2:AF'+sh1.getLastRow()).getDisplayValues().flat();
const data=sh1.getRange(2,1,sh1.getLastRow()-1,32).getDisplayValues();
shs.forEach(sh=>{
if (dts.includes(sh.getName())){
let temp_data = data.filter(r=>r[31]==sh.getName());
sh.getRange(sh.getLastRow()+1,1,temp_data.length,temp_data[0].length).setValues(temp_data);
}});
}

How can I duplicate value into Gsheets with apps script?

I like to modify this script to duplicate a single record as many times as I need into gsheets.
As of now my script below just posts a single record as intended.
Now I like to add a variable(quantity) and have the record posted(append) onto the sheet as many times as the quantity variable says.
function sendProductIDtoQRCodeSheet(fieldData){
var sheet = SpreadsheetApp.openById('1fUNWqzHosafsdafasdfas--B4').getSheetByName('LISTS');
var lRow = sheet.getLastRow();
var range = sheet.getRange("A1:A");
var value1 = fieldData.productId;
var value2 = fieldData.productName;
var joinedValues = value1 + " | " + value2;
sheet.appendRow([
fieldData.productId, joinedValues
]);
}
In order for you to achieve what you want, you can use a for loop and a take a look at these methods since they can be of help:
getRange(row, column, numRows, numColumns) - for getting the range with the given numbers of rows and columns;
setValues(values) - for setting the values on the specified range for which they must match the dimensions of it;
You might also want to search similar questions here on Stack which can be of help.
Is there a reason you can't use a loop?
Check the documentation for the sheet class. There's a method called insertRows(rowIndex, numRows) that could be what you're looking for. It inserts a number of blank rows equal to numRows at the index. You can just populate them after you make them.

Auto-expanding formula and then copy paste values

I have a function that adds an auto-expanding formula to some Header row cell
In the next line of code, I get the Display Values and then post them back to the sheet
I am concerned that I will be getting the values in the range of the auto-expanding formulas before they have finished expanding
Will this r.getDisplayValues(); get the values before the auto-expansion has finished? thereby getting values with blank data that should have data
I have tested various scenarios, but this is not definitive
Also, I have not been able to find anything in searching on this
Thank you
function setFormulasAE_n() {
var ss =SpreadsheetApp.getActive();
var sheet =ss.getSheetByName('Elements');
var LC = sheet.getLastColumn();
var LR = sheet.getLastRow();
//Auto-expanding Formulas to be added
//Two dim array with 1 row
var formulas = [[
"=ArrayFormula({\"ig_TagsHistorical\";iferror(vlookup(INDIRECT(\"Elements!A2:A\"&counta(Elements!$AJ$1:$AJ)), \'HelperElements_(ignore)\'!$A$2:$G, {5}, 0))})",
"=ArrayFormula({\"Additional Networks\";iferror(vlookup(INDIRECT(\"Elements!A2:A\"&counta(Elements!$AJ$1:$AJ)), \'Helper_(ignore)\'!$A$2:$D, {4}, 0))})",
]];
//Add auto-expanding formulas to Cell(s)
var cell = sheet.getRange(1,LC+1,1,formulas[0].length);
cell.setFormulas(formulas);
//Get range and post back Display Values
var r = sheet.getRange(1,LC+1,LR,formulas[0].length);
var v = r.getDisplayValues();
r.setValues(v)
}
The setFormulas() function you have used in the script is synchronous which essentially means that the code following the instruction won't be executed until the functions finishes the execution.
Therefore, r.getDisplayValues() will always get the values after the auto-expansion.
What you could do to make sure that the values you get are the expected ones is to use the flush() function after the cell.setFormulas(formulas) line of code. What flush() does is basically applying all the pending Spreadsheet changes - more specifically the formulas you need to set.
Furthermore, you can check these links since they might be of help to you:
SpreadsheetApp Class - flush();
Range Class - setFormulas().

Selecting a range from within a named range google sheets script

Is there any way to select a range specified from within another named range?
For example if I have a named range say "firstRange" and I want to get just the 2nd column from within that range to copy to another named range.
I can do this by copying cells individually with a for loop but it takes a long time.
Unfortunately
getRangeByName("firstRange").getRange(1,1,2) is not valid as getRange is not a method of getRangeByName
Thanks
How about this method? I think that there are several answers for your situation. So please think of this as one of them. The flow is as follows.
Flow:
Range of values you want is retrieved from the source named-range by offset().
When getLastRow() is used to the named range, the returned value is the last row of the named range.
Retrieve the destination named-range.
Copy the retrieved source range to the destination range.
Sample script:
In this sample script, the 2nd column of named range of firstRange is copied to the named range of secondRange.
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Retrieve source range.
var sourceRange = ss.getRangeByName("firstRange");
var src = sourceRange.offset(0, 1, sourceRange.getLastRow(), 1);
// Retrieve destination range.
var destinationRange = ss.getRangeByName("secondRange");
// Copy from source range to destination range.
src.copyTo(destinationRange);
For example, if you want to copy the retrieved source range to 2nd column of the destination range, please modify var destinationRange = ss.getRangeByName("secondRange") as follows.
var destinationRange = ss.getRangeByName("secondRange").offset(0, 1, 1, 1);
References:
getLastRow()
offset()
copyTo()
If this was not what you want, I'm sorry.
You can get the named range and then get the values which returns a multi-dimensional array. Then you can parse the values that you are looking for out of that. For example
var parsed = SpreadsheetApp.getActiveSpreadsheet()
.getRangeByName('namedRange')
.getValues()
.map(function(row) {
return [row[1]];
});
This gets all the values from your named range and then maps over them and grabs the values from the second column (the 1 index in this example)
I hope that helps.

Google apps script getRange() range not found error

I feel like I'm going about this in all the wrong way. I'm trying to automate some of my workload here. I'm cleaning up spreadsheets with 4 columns (A-E), 2000+ rows. Column B contains website URLs, column D contains the URL's business name, generated from another source.
Sometimes the tool doesn't grab the name correctly or the name is missing, so it populates the missing entries in column D with "------" (6 hyphens). I've been trying to make a function that takes an input cell, checks if the contents of the cell are "------", and if it is the function changes the contents of the input cell to the contents of the cell two columns to the left (which is generally a website url). This is what I've come up with.
function replaceMissing(input) {
var sheet = SpreadsheetApp.getActiveSheet();
//sets active range to the input cell
var cell = sheet.getRange('"' + input + '"');
//gets cell to fill input cell
var urlCell = sheet.getRange(cell.getRow(), cell.getColumn() - 2);
//gets contents of input cell as String
var data = cell.getValue();
//gets contents of urlCell as String
var data2 = cell.getValue();
//checks if input cell should be replaced
if (data === "------") {
//set current cell's value to the value of the cell 2 columns to the left
cell.setValue(data2);
}
}
When I attempt to use my function in my sheet, the cell is returning the error
Error Range not found (line 4).
I'm assuming, based on similar questions people have asked, that this is how you use the A1 notation of the function with an argument. However, that doesn't seem to be the case, so I'm stuck. I also don't think my solution is very good period.
1) It's somewhat ambiguous in GAS documentation, but custom functions have quite a few limitations. They are better suited for scenarios where you need to perform a simple calculation and return a string or a number type value to the cell. While custom functions can call some GAS services, this practice is strongly discouraged by Google.
If you check the docs for the list of supported services, you'll notice that they support only some 'get' methods for Spreadsheet service, but not 'set' methods https://developers.google.com/apps-script/guides/sheets/functions
That means you can't call cell.setValue() in the context of a custom function. It makes sense if you think about it - your spreadsheet can contain 1000s of rows, each with its own custom function making multiple calls to the server. In JavaScript, every function call creates its own execution context, so things could get ugly very quickly.
2) For better performance, use batch operations and don't alternate between read / write actions. Instead, read all the data you need for processing into variables and leave the spreadsheet alone. After processing your data, perform a single write action to update values in the target range. There's no need to go cell by cell when you can get the entire range using GAS.
Google Apps Script - best practices
https://developers.google.com/apps-script/guides/support/best-practices
Below is a quick code example that runs onOpen and onEdit. If you need more flexibility in terms of when to run the script, look into dynamically-created triggers https://developers.google.com/apps-script/reference/script/script-app
Because your spreadsheets have lots of rows, you may hit the execution quota anyway - by using triggers you can work around the limitation.
Finally, if a cell containing '----' is a rare occurrence, it might be better to create another array variable with new values and row numbers to update than updating the entire range.
Personally, I think the single range update action would still be quicker, but you could try both approaches and see which one works best.
function onOpen(){
test();
}
function onEdit() {
test();
}
function test() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('yourSheetName');
//range to replace values in
var range = sheet.getRange(2, 4, sheet.getLastRow() - 1, 1);
//range to get new values from
var lookupRange = range.offset(0, -2);
//2d array of values from the target range
var values = range.getValues();
//2d array of values from the source range
var lookupValues = lookupRange.getValues();
//looping through the values array and checking if array element meets our condition
for (var i=0; i < values.length; i++) {
values[i][0] = (values[i][0] == '------') ? lookupValues[i][0] : values[i][0];
}
// one method call to update the range
range.setValues(values);
}

Categories