How to place 2d array with different lengths in Google Sheets Script? - google-sheets

I'm sending 2d array to google sheets using json:
{"rep":[["a3289035","b656011929551"],["brown","realistic","yellow"]]}
Then I'm getting this array in google script:
var parsedJson = JSON.parse(e.postData.contents);
var repList = parsedJson.rep;
And everything is ok, but when I'm trying to place this array on the sheet:
sheet.getRange(row + 1, 2, repList[0].length, 1).setValues(repList[0]);
sheet.getRange(row + 1, 3, repList[1].length, 1).setValues(repList[1]);
I'm getting the error:
Cannot convert Array to Object[][]
What is wrong?
I found that this code can do it:
repList[0] = repList[0].map(function(e){return [e];});
repList[1] = repList[1].map(function(e){return [e];});
sheet.getRange(row + 1, 2, repList[0].length, 1).setValues(repList[0]);
sheet.getRange(row + 1, 3, repList[1].length, 1).setValues(repList[1]);
but how to make it work for 2d array with any lengths and any subarray's lengths?
Expected result:

Vosnim,
Try something like this:
var repList = parsedJson.rep;
var sheet = SpreadsheetApp.getActive().getSheetByName('TEST')
var startCol = 1;
repList.forEach(function (r, i) {
var values = r.map(function (ro) { return [ro]});
sheet.getRange(1, startCol + i, values.length, 1).setValues(values)
})

Related

Merging categories on Google Sheets but data will be unclear

Sorry in advanced for bad English. I have 15+ rows and 15+ columns. I need to merge cells in a spreadsheet but data will be unclear after merging. Is there a formula/function I can use to clarify my merge? Thank you in advance.
Please view example spreadsheet.
This is not possible using only formula. Formulas can't do anything to formatting like merging cells and applying colors to text. But here's a script you can use to begin with. You may modify it to apply font color.
Try:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSheet = ss.getSheetByName("Sheet1");
var destSheet = ss.getSheetByName("Sheet2");
var sourceData = sourceSheet.getRange(2, 1, sourceSheet.getLastRow() - 1, sourceSheet.getLastColumn()).getValues();
//Add name to each column, add here if you have more columns
sourceData.forEach(function (x) {
x[2] = x[1] + ":\n" + x[2]
x[3] = x[1] + ":\n" + x[3]
});
var array = sourceData,
hash = {},
i, j,
result,
item,
key;
for (i = 0; i < array.length; i++) {
item = array[i];
key = item[0].toString();
if (!hash[key]) {
hash[key] = item.slice();
continue;
}
for (j = 1; j < item.length; j++) hash[key][j] = hash[key][j] + "\n" + item[j];
}
result = Object.values(hash);
destSheet.getRange(2, 1, result.length, result[0].length).setValues(result);
}
Result:
Reference:
How to merge an array of arrays with duplicate at index 0 into one array javascript
Hope this helps!
You can try with this formula, adapt the column range in the MAP function if necessary:
={UNIQUE('raw data'!A2:A),
SCAN(,UNIQUE('raw data'!A2:A),
LAMBDA(x,city, BYCOL(IFNA(FILTER ({'raw data'!B2:B,
MAP('raw data'!C2:G,LAMBDA(a,IF(a="","",INDEX('raw data'!B:B,ROW(a))&":"&CHAR(10)&a)))},'raw data'!A2:A=city,'raw data'!A2:A<>"")),LAMBDA(col,JOIN(CHAR(10)&CHAR(10),col)))))}

getRange variable range

I am trying to use a google sheet to rank a list of elements. This list is continually updated, so it can be troublesome to update the list if i already have hundreds of elements ranked and need to rank 10 new ones. Rather than having to re-rank some of the previously ranked elements every time (whether manually or using formulas), i thought it easier to write a macro that would re-rank for me.
1 - element A
2 - element B
3 - element C
new element: element D
For instance if i wanted element D to be ranked 2nd, i would need to change element B to 3 and element C to 4. This is tedious when doing hundreds of elements.
Here is my code so far but I get stuck with the getRange lines. Rankings are in column A.
function RankElements() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
var r = s.getActiveCell();
var v1 = r.getValue();
var v2 = v1 + 1
var v3 = v2 + 1
var lastRow = s.getLastRow();
s.getRange(1,v2).setValue(v2);
s.getRange(1,v3).autoFill(s.getRange(1,v3+":"+1,lastRow), SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES);
s.getRange(1,v3+":"+1,lastRow).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
s.getFilter().sort(1, true);
};
You can do the following:
Iterate through all values in column A.
For each value, check if (1) ranking is equal or below the new one, and (2) it's not the element that is being added.
If both these conditions are met, add 1 to the current ranking.
It could be something like this:
function RankElements() {
const sheet = SpreadsheetApp.getActiveSheet();
const cell = sheet.getActiveCell();
const row = cell.getRow();
const newRanking = sheet.getActiveCell().getValue();
const firstRow = 2;
const columnA = sheet.getRange(firstRow, 1, sheet.getLastRow() - 1).getValues()
.map(row => row[0]); // Retrieve column A values
for (let i = 0; i < columnA.length; i++) { // Iterate through column A values
if (columnA[i] >= newRanking && (i + firstRow) != row) {
sheet.getRange(firstRow + i, 1).setValue(columnA[i] + 1); // Add 1 to ranking
}
}
sheet.getFilter().sort(1, true);
};

How to grab all non-empty cell data in row - Google Sheets Script Editor

I'm not sure if this is even possible, and to be quite honest, I haven't tried many things because I wasn't sure where to even start. I'm using the Script Editor from Google Sheets, btw. I know there are SpreadsheetApp.getRange() and another to get the values or something like that. But what I want is a bit specific.
Is there a way to grab all the cell data in a given row and put it into an array? The rows will vary in size, that's why I can't do an exact range.
So for example, if I were to have rows have these values:
abc | 123 | 987 | efg
blah| cat | 654
I want to be able to grab those values and place them into an array like ["abc", "123", "987, "efg"]. And then if I run the function on the next row, it'd be ["blah", "cat", "654"].
Actually, it can be placed into any data type as long as there's a delimiter I'd be able to use.
Thank you in advance!
This is easier to achieve without a script, with the formula =filter(1:1, len(1:1)) returning all values in nonempty cells in row 1, etc.
From a script, you can do something like this:
function flat_nonempty() {
var range = SpreadsheetApp.getActiveSheet().getRange("A:A"); // range here
var values = range.getValues();
var flat = values.reduce(function(acc, row) {
return acc.concat(row.filter(function(x) {
return x != "";
}));
}, []);
Logger.log(flat); // flat list of values, no blanks
}
The range here can have one row or multiple rows.
Is this useful for you?
When there are abc | 123 | 987 | efg, blah| cat | 654 and abc | | 987 | efg at row 1, row 2 and row 3, myFunction(1), myFunction(2) and myFunction(3) return [abc, 123.0, 987.0, efg], [blah, cat, 654.0] and [abc, , 987.0, efg].
function myFunction(row){
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange(row, 1, 1, ss.getLastColumn()).getValues();
var c = 0;
for (var c = values[0].length - 1; c >= 0; c--){
if (values[0][c] != "") break;
}
values[0].splice(c + 1, values[0].length - c - 1);
return values[0];
}
Adapted from https://developers.google.com/apps-script/reference/spreadsheet/sheet#getDataRange()
You can loop and create your array directly if you do want an array. Here I am illustrating creating for all the rows, but if you know the row you want, you could do without the outer loop (and just set i to the desired row).
function rowArr() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// This represents ALL the rows
var range = sheet.getDataRange();
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
var row = [];
for (var j = 0; j < values[i].length; j++) {
if (values[i][j]) {
row.push(values[i][j]);
}
}
Logger.log(row);
}
}

run onedit on active sheet

I have looked for an answer for this question, but I am exceptionally green to even rudimentary scripting so I have not been able to understand what I have found.
I have a spreadsheet we are using for a worklist - it is separated into three tabs: Samples / Images / Archive
Users access the spreadsheet to collect items to work - once it is complete they mark Column A as "Complete", and I have code very helpfully provided by ScampMichael to automatically move the row to the "Archive" sheet once they do so:
function onEdit(event) {
// assumes source data in sheet named Samples
// target sheet of move to named Archive
// test column with Completed is col 1 or A
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Samples" && r.getColumn() == 1 && r.getValue() == "Complete") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Archive");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);
}
}
My challenge is that I can not get this code to work simultaneously for both the "Samples" and the "Images" tab. I gather that this is because you can not have more than one onEdit function per spreadsheet, but so far my efforts to expand the code to look at both tabs has failed.
Any help that can be provided is extremely appreciated!
It is still not exactly clear what you want to do, but I took a shot at it. I assumed the image is on the same row number as the completed row on samples and when copying the image to archive I append it to the end of the row created in the archive. I made some changes to your code. They are noted. If you need it, I can share my testing spreadsheet.
function onEdit(event) {
// assumes source data in sheet named Samples
// target sheet of move to named Archive
// test column with Completed is col 1 or A
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if(s.getName() == "Samples" && r.getColumn() == 1 && r.getValue() ==
"Complete") {
var row = r.getRow();
var numColumns = s.getLastColumn();
var targetSheet = ss.getSheetByName("Archive");
var target = targetSheet.getRange(targetSheet.getLastRow() + 1, 1);
s.getRange(row, 1, 1, numColumns).moveTo(target);
s.deleteRow(row);//changed to delete the row
image(row,numColumns)//call function to process Images send row and last column
}}
function image(row,numColumns){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1=ss.getSheetByName("Images")//get Images sheet
var lc=s1.getLastColumn()
var data=s1.getRange(row, 1, 1,lc)//get row
var targetSheet = ss.getSheetByName("Archive");
var target = targetSheet.getRange(targetSheet.getLastRow() , numColumns+1,1,1);//add image one column after Samples data on same row.
s1.getRange(row, 1, 1, lc).moveTo(target);
s1.deleteRow(row)//delete the copied image row.
}

moveTo messing up cells outside the range

im rearranging a spreadsheet using google script. I am first sorting it and then moving some rows within a range to the end of the sheet. Everytime i do the moveTo function some cells that reference the moved rows get changed to reflect the new row numbers even though the cells are outside my range and should not be modified. For example if im moving cell b3 and i have cell f4 =b3 then when i move b3 cell f4 changes to be whatever b3 is now. i tried locking it with =b$3 but still didnt work. Also it messes up the conditional formatting that should be in place for the entire column using something like "d2:e" and it changes to be something like "d2:e109" or something similar. Any clue whats going on?
function onEdit(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var allowedSheet = 1;
if(sheet.getIndex() == allowedSheet) {
var editedCell = sheet.getActiveCell();
var sortBy = [1, 3, 2];
var triggerCol = [1,2,3,10,11,12];
var rangeStart = "A";
var rangeEnd = "E";
var tableRange = "A2:E";
if(triggerCol.indexOf(editedCell.getColumn()) > -1) {
var range = sheet.getRange(tableRange);
range.sort([{column: sortBy[0], ascending: true}, {column: sortBy[1], ascending: false}, {column: sortBy[2], ascending: true}]);
var indexOfIncome = find( sheet, 2, "Income");
if( indexOfIncome > 0 ){
var overflowRange = sheet.getRange("A2:E" + (indexOfIncome - 1 ));
var lastRow = findFirstEmptyCell( sheet, 1 );
overflowRange.moveTo(sheet.getRange("A" + ( lastRow )));
var fullRange = sheet.getRange(rangeStart + indexOfIncome + ":" + rangeEnd);
fullRange.moveTo(sheet.getRange(rangeStart + "2"));
}
}
}
}
function find( sheet, column, value ) {
var data = sheet.getRange(1, column, sheet.getMaxRows()).getValues();
for( i = 0; i < sheet.getMaxRows(); i++ ){
if (data[i][0].toString().indexOf(value) > -1 ) {
return i + 1;
}
}
return 0;
}
function findFirstEmptyCell ( sheet, column ){
var data = sheet.getRange( 1, column, sheet.getMaxRows() ).getValues();
for( i = 0; i < sheet.getMaxRows() ; i++ ){
if( data[i][0] == "" ){
return i + 1;
}
}
return 0;
}
It's an expected behaviour, moveTo works just like Cut (Ctrl+X), while your looking for Copy+Paste then delete original content, which should be copyTo(newRange) associated with clear(oldRange).
I got it to work by using copy and then clearing. Now why in the world google would make a single function that behaves the exact opposite from all the other functions is beyond me. The documentation says moveto behaves as cut and paste but when i cut and paste i do not mess up the rest of my sheet.

Resources