How to Merge rows in Google Sheets - javascript

I tried digging this stuff and have no solution so I'm hoping someone can assist. I have a sheet with the following:
Data
123|456|789
111|222|333
etc...
Result Needed
|123 456 789|
|111 222 333|
etc...
I'm trying to avoid formulas (=Concat) and (=A2&" "&B2&" "&C2) etc...
I tried sheet.getRange(2,1,1,2).mergeAcross(); and it merged the cells and kept he left-most value. Google searches point to the formula solution.

You can try Array.join() for each row:
Snippet:
var jointRowsArray = sheet
.getRange(2, 1, 2, 3) //A2:C4
.getValues()
.map(function(row) {
return [row.join(' ')];//join Each row
});
sheet.getRange(2, 4, jointRowsArray.length, 1).setValues(jointRowsArray);
To Read:
Arrays
Array#join
Array#map
Range#setValues
Best Practices

var range = sheet.getRange(2,1,1,2)
var values = range.getValues()
range.clearContent()
sheet.getRange(2,1).setValue(values.join(' '));
You can pull the values into JS and then join and insert them into a single cell. You can also place this inside an iterator and do something akin to getRange(i,1,1,2). This can be triggered manually or by an edit hook.
However, this seems like the perfect fit for a single formula.
=TRANSPOSE(QUERY(TRANSPOSE(A:C),,COLUMNS(A:C)))
The formula would go on the first row in perhaps column D. JOIN functions cannot usually be arrayed in google sheets, and you would normally have to put a formula in for every row. However, here we are tricking the sheet into thinking our data is the header and displaying it accordingly.

Related

Break multiple value cell into different rows via Apps Scripts

I'm looking to split a cell that have multiple values separated by "\n" via App Script. Itried a few functions that were proposed but nothing seemed to work. Below you can see a sample format and desired outcome.
Thanks.
I believe your goal is as follows.
You want to split each cell value with \n using Google Apps Script.
You want to put the split values in the vertical direction.
In this case, how about the following sample script?
Sample script:
function myFunction() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1"); // Please set your sheet name.
const range = sheet.getRange("A2:A" + sheet.getLastRow());
const values = range.getValues().flatMap(([a]) => a ? a.split("\n").map(e => [e.trim()]) : []);
range.offset(0, 1, values.length).setValues(values);
}
When this script is run, the source values are retrieved from the column "A". And, the converted values are put in column "B".
Reference:
flatMap()

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

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

How do I exclude a header row from getDataRange()?

I want to fetch the range of all cells with data in it, but not including the header row.
I found that getDataRange() captures all the cells with data in it, and to avoid including the first row, I thought I could just delete the first element in what I am assuming is an array?
How can I do this/ is there a more elegant way to approach this problem?
You want to retrieve the values without the 1st row using getDataRange().
If my understanding is correct, how about this sample script? Please think of this as just one of several answers.
Sample script 1 :
In this sample, offset() is used. In this case, before the values are retrieved, the range is modified.
var sheet = SpreadsheetApp.getActiveSheet();
var headerRowNumber = 1;
var values = sheet.getDataRange().offset(headerRowNumber, 0, sheet.getLastRow() - headerRowNumber).getValues();
Logger.log(values)
Sample script 2 :
In this sample, shift() is used. In this case, after the values were retrieved, the values are modified.
var sheet = SpreadsheetApp.getActiveSheet();
var values = sheet.getDataRange().getValues();
values.shift();
Logger.log(values)
References:
offset()
shift()
If I misunderstood your question and this was not the direction you want, I apologize.
If you want the header and the rest of the values separately
var values=SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var header=values.splice(0,1);
Logger.log(values);
Logger.log(header);

Google Apps Script - how to reference object in loop in function

I am trying to create a small invoicing organization system using google sheets/drive. I have one sheet I call "tasks", where I plan to control everything from. Some of my columns include, "Client", "Project", "Requirements", "Details", "subcontractor"... As I acquire new tasks/clients, i'd find and append information respective of the task ("Project", "Requirements") to other sheets or, if none exist, create the folders, sheets, and append the respective necessary information from the "tasks" sheet to the new sheets. Some of the sheets will be sent to subcontractors, dependent on whether or not their tasks were updated or new ones were assigned to them in the original "tasks" sheet.
Within the sheets I send to subcontractors, there will be fields for them to fill out (rate, eta..), once filled, I will send that info to a third sheet to apply some margins, extra fees, and then send the info back to the original "tasks" sheet where it will fill appropriate cells.. Once all of the necessary information in a row is filled, it will be prepared and organized into an invoice for the client specified in the "client" column...
Anyways, i've been trying to learn javascript to implement all of this. As I plan to create folders, sheets, and append information based on the values entered in the rows and columns of the "tasks" sheet... I've placed a for loop in an onEdit function that does the following:
function onEdit(e) {
var ss = e.range.getSheet().getParent();
var sheet = e.range.getSheet();
var row = e.range.getRow();
var columns = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//assign titles as 'keys' to array
var titles = []
//assign values of edited row to array
var values = []
//create an object to associate the title to the new edited values
var task = {}
for(var i in columns){
titles.push(sheet.getRange(1, columns[i]).getValue()); //push titles
values.push(sheet.getRange(row, columns[i]).getValue()); //push values of updated row
task[titles[i]] = values[i]; //add the values to their property names in task object
}
This works, and I can reference task["Client"], but i'd like to put this loop in a function so that I can use it again. I suppose I could do without it, but array "columns" only represents the columns I will be inputting on the "first round" --- when im sending information out...I will be inputting new information to columns 10-15, then 16-20, as the tasks progress.. and i'd like to run the for loop for those columns without having to create separate loops. To do this i've created the GetInfo function below:
function GetInfo(row,column){
for(var i in column){
titles.push(sheet.getRange(1, column[i]).getValue()); //push titles
values.push(sheet.getRange(row, column[i]).getValue()); //push values of updated row
this.task[titles[i]] = values[i]; //add the values to their task
}
}
What I am trying to accomplish is similar to what is outlined here. However, the "for(var..in..") is not mentioned in the examples and I think im missing something. In attempt to use the function for the first array of columns ive done this:
var list = new GetInfo(row,columns);
i'd like to reference the task as follows
list.task["client"]
Or var.task["name"], but the above doesn't work. When I toast list.task["Client] or try to append it to another cell, nothing happens - its blank. What am I doing wrong? How do I accomplish this correctly? What should I do?
Any help or guidance would be greatly appreciated. Please.
(other toasts are working, and the respective cell is not blank, without the function the for var in works)

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