Reorder multiple ranges in Google Sheets with apps script? - google-sheets

Recently I got automatic reorder code working for range DY29:EA43 (thanks to you), but I want also automatically reorder additional multiple ranges but I can't figure it out how I can run multiple functions in Apps Script?
Ranges I want to automatic reorder in addition:
Range DY47:EA60 by percentage in column EA (EA47:EA60).
Range DY65:EA77 by value in column EA (EA65:EA77).
Range DY82:EA95 by percentage in column EA (EA82:EA95).
Range DY99:EA113 by percentage in column EA (EA99:EA113).
Code I have now:
function myFunction(){
sample();
}
function myFunction() {
const sheet = SpreadsheetApp.getActiveSpreadsheet();
const a1Notations = ["B3:J7", "B11:J15", "B19:J23", "B27:J31", "B35:J39", "B43:J47", "B51:J55", "B59:J63"];
const ranges = sheet.getRangeList(a1Notations).getRanges();
const formatRanges = a1Notations.map(r => r.replace(/^./, "J"));
sheet.getRangeList(formatRanges).setNumberFormat("0");
ranges.forEach(r => r.sort({ column: r.getLastColumn(), ascending: false }));
sheet.getRangeList(formatRanges).setNumberFormat("#");
}
function sample() {
const sheet = SpreadsheetApp.getActiveSpreadsheet();
const range = sheet.getRange("EA29:EA43");
const formulas = range.getFormulas();
const mFormulas = formulas.map(([ea]) => [ea.replace(/=SUM\(([A-Z]+)([0-9]+)\+([A-Z]+)([0-9]+)\)/i, "=SUM($$$1$$$2+$$$3$$$4)")]);
if (JSON.stringify(formulas) != JSON.stringify(mFormulas)) range.setFormulas(mFormulas);
SpreadsheetApp.flush();
sheet.getRange("DY29:EA43").sort({ column: 131, ascending: false });
}
Link to sheet: https://docs.google.com/spreadsheets/d/17ZyUuDAp85Vr76NMmqQXuLqTUMMuHP6sQu2gvQREhDU/edit#gid=446993580

#1
Try
function reOrder() {
const sheet = SpreadsheetApp.getActiveSpreadsheet();
["DY47:EA60", "DY65:EA77", "DY82:EA95", "DY99:EA113"].forEach(rng => {
let values = sheet.getRange(rng).getValues()
values = values.sort(function (a, b) {
return a[2] - b[2];
});
sheet.getRange(rng).setValues(values)
})
}
#2
or to keep formulas
function reOrder() {
const sheet = SpreadsheetApp.getActiveSpreadsheet();
["DY47:EA60", "DY65:EA77", "DY82:EA95", "DY99:EA113"].forEach(rng => {
sheet.getRange(rng).sort([{ column: 131, ascending: true }]);
})
}
#3
last solution, according to the complixity of your spreadsheet, in EC47
=sort(arrayformula({DY47:DZ60,(EA47:EA60)*1}),3,1)
same for other ranges
you will need to show all rows

Related

Google sheets How to create a custom function

I have two sheets
Sheet1
and sheet2
What I want to do is one by one copy the values of each row from sheet 1 to sheet 2. whenever one row is copied from sheet1 to sheet2 (i.e values from cell A to F). the cell G2 and H2 (in sheet2) get values ( based on multiple different formulas filter function, pivit tables etc from other sheets ) When I get the updated values in sheet 2 I want to copy the values form G2 and H2 to the respective row back in sheet 1
basically I want to use the sheet2 as a custom function where cell A2, B2, C2, D2, H2, F2 are inputs and cell G2 and H2 are outputs.
I tried using macros but what is required is a combination of both Absolute and Relative referencing and that doesn't seem to be possible
gif of the process I am doing manually
Did it with appscript and menue items
function onOpen() {
var ui = SpreadsheetApp.getUi();
// Or DocumentApp or FormApp.
ui.createMenu('AERON')
.addItem('Calculate Single Cabnets', 'menuItem1')
.addSeparator()
.addItem('Calculate Multiple Cabnets', 'menuItem2')
.addToUi();
}
function menuItem2() {
var sheet = SpreadsheetApp.getActiveSheet();
var selection = sheet.getSelection();
if(selection.getActiveRange().getA1Notation()==null)
ui.alert('No range selected');
else{
//get the inputs
var sheetname = SpreadsheetApp.getActiveSheet().getName();
// SpreadsheetApp.getUi().alert(sheetname);
var range = SpreadsheetApp.getActiveSpreadsheet().getRange(selection.getActiveRange().getA1Notation());
var input = range.getValues();
var ac = SpreadsheetApp.getActiveSpreadsheet().getActiveRange()
SpreadsheetApp.getUi().alert(ac);
// get sheet name
sheetname = SpreadsheetApp.getActiveSheet().getName()
const numRows = ac.getNumRows();
for (let i=1; i<= numRows;i++ ){
var cell = ac.getCell(i, 1).offset(0, 0,1,6).getA1Notation();
var cell1 = ac.getCell(i, 1).offset(0, 7,1,2).getA1Notation();
// SpreadsheetApp.getUi().alert(cell + " " +sheetname + " " + cell1);
single_row(sheetname, cell, cell1 );
}
SpreadsheetApp.getUi().alert("All values updated")
}
}
function single_row(sheetName, inputRange, outputRange ) {
var fnSheetName = "Main";
var fnInputRange = 'A2:F2';
var fnOutputRange = 'G2:H2';
var input = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName).getRange(inputRange).getValues();
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(fnSheetName).getRange(fnInputRange).setValues(input);
var output = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(fnSheetName).getRange(fnOutputRange).getValues();
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName).getRange(outputRange).setValues(output);
}
This creates a menue item on the top which one by one copies the values to the other sheet then copies the result back

Data from a Google Sheet "form" to a Google sheet spreadsheet

I have a Google sheet that is in a “Form” format. I need to program a button that once the sender completes the form, will send the data to another sheet in a spreadsheet format and erase the data from the “form” making it ready for another form entry.
enable goole sheets api service, and adapt this script (names of source and destination sheets, and cellsA1Notation of data in source sheet).
function onOpen() {
SpreadsheetApp.getUi().createMenu('⇩ M E N U ⇩')
.addItem('👉 Validate Unit Registration (FORM)', 'copyDataRegistrationForm')
.addItem('👉 Reset Unit Registration (FORM)', 'initRegistrationForm')
.addToUi();
}
function copyDataRegistrationForm() {
const ssId = SpreadsheetApp.getActiveSpreadsheet().getId();
const srcSheet = "Unit Registration (FORM)";
const dstSheet = "Master Data";
const rngA1Notation = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dstSheet).getRange(1, 1, 1, 74).getValues().flat()
const src = rngA1Notation.map(e => `'${srcSheet}'!${e}`);
const values = Sheets.Spreadsheets.Values.batchGet(ssId, { ranges: src })
var data = []
values.valueRanges.forEach(e => data.push(e.values ? e.values.flat().toString() : ""))
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dstSheet).appendRow(data)
}
function initRegistrationForm() {
const srcSheet = "Unit Registration (FORM)";
const dstSheet = "Master Data";
const rngA1Notation = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(dstSheet).getRange(1, 1, 1, 74).getValues().flat()
var rng = rngA1Notation.filter(r => SpreadsheetApp.getActiveSpreadsheet().getRange(r).getFormula() == '')
SpreadsheetApp.getActiveSpreadsheet().getSheetByName(srcSheet).getRangeList(rng).clearContent()
}

Setting a column to equal the negative of a row in Google Sheets

The Google Sheets API seems vague and I'm probably just too tired.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var positives = sheet.getRange("D3:AG3");
var negatives = sheet.getRange("C4:C33");
for (i=0;i<positives.getLastColumn();i++) {
var j = positives[i]*-1;
negatives[i].setValue(j);
}
}
I'm sure I'm doing eight things wrong but if someone is more familiar with Google Sheets, please throw a brick at me.
First, positives is a ranges, and you need to use getValues() to get an array that you can manipulate.
Second, it's not recommended to use Sheets API methods inside loops, the best practice is to manipulate arrays in loops and then use single get and set values API to read / write to a range.
Sample Code:
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var positives = sheet.getRange("D3:AG3").getValues();
var negatives = sheet.getRange("C4:C33");
var result = [];
for (i = 0; i < positives[0].length; i++) {
result.push([positives[0][i] * -1]);
}
negatives.setValues(result);
}
Sample Output: (I only put values in three rows)
Reference:
push()
Avoid using onEdit for these kind of changes as it will be resource intensive. You are changing all the values of the column into negative of the row EVERY TIME you edit the sheet (Unless that should be the case)
If you really want to use onEdit, be sure to limit it only when the specific range is edited.
Code:
function onEdit(e) {
const row = e.range.getRow();
const column = e.range.getColumn();
// if edited range is within D3:AG3
if(row == 3 && column >= 4 && column <= 33) {
// write to the corresponding row (invert col and row)
e.source.getActiveSheet().getRange(column, row).setValue(e.value * -1);
}
}
Note:
Behaviour of the onEdit function is that when you edit the range D3:AG3, it will negate its value and write into its corresponding destination, one by one.
If you edit D3, it will assign that negative value into C4, nothing more.
If you edit outside the positive range, it will not do anything.
Another approach is to copy your positive row into negative column by transforming your data structure into the destination by bulk.
Code:
function rowToColumn() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var pRange = sheet.getRange("D3:AG3");
var pValues = pRange.getValues();
// pValues is a 2D array now
// row range values = [[1, 2, 3, ...]
var negatives = sheet.getRange("C4:C33");
// column range values = [[1], [2], [3], ...]
// since structure of row is different than column
// one thing we can do is convert the row into column structure
// and multiply each element with -1, then assign to negatives
pValues = pValues.map(function(item) {
item = item.map(function(col) {
return [col * -1];
});
return item;
})[0];
// set values into the negatives range
negatives.setValues(pValues);
}
Note:
Behaviour of the rowToColumn function is that it transfers all the values of the row range and then put it into negatives range all at once.
Blank cells will yield 0 by default, add a condition on return [col * -1]; if you want blank cells to return other values instead.
Output:

Google Sheets Macro not pasting formula

Please help with Google sheets macro. Trying simple stuff as a start. Macro needs to clear B2 cell and paste formula (importhtml) as shown below. Then it has to do the same for cell B43 (Potentially will do this in multiple cells on this sheet if it ever works).
Google sheets link:
https://docs.google.com/spreadsheets/d/1O0AJJEd0tGyFXlEPhih8HG9YOWcZNRot_Z7FXPdQrx8/edit?usp=sharing
Strangely it works in B43, but leaves B2 empty.
function Untitledmacro() {
// Clear B2 and then paste formula
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DATA'), true);
spreadsheet.getRange('B2').activate();
spreadsheet.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
};
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DATA'), true);
spreadsheet.getRange('B2').activate();
spreadsheet.getCurrentCell().setFormula('=IMPORTHTML("https://www.investing.com/commodities/real-time-futures","table",1)');
// Clear B43 and then paste formula
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DATA'), true);
spreadsheet.getRange('B43').activate();
spreadsheet.getActiveRangeList().clear({contentsOnly: true, skipFilteredRows: true});
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DATA'), true);
spreadsheet.getRange('B43').activate();
spreadsheet.getCurrentCell().setFormula('=IMPORTHTML("https://www.investing.com/indices/indices-futures","table",1)');
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('DATA'), true);
Added Sheets API, not sure it's needed here though
See if this helps
function clearAndSetformula() {
const sheet = SpreadsheetApp.getActive().getSheetByName('DATA');
const cells = ["B2", "B43"];
const formulas = [
'=IMPORTHTML("https://www.investing.com/commodities/real-time-futures","table",1)',
'=IMPORTHTML("https://www.investing.com/indices/indices-futures","table",1)'
];
cells.forEach((cell, i) => sheet.getRange(cell).clear({ contentsOnly: true, skipFilteredRows: true }).setFormula(formulas[i]));
};
If you need to add more cells/formulas, add them in the proper arrays. Make sure the positions line up so that the first cell in the array cells will have the first formula in the array formulas etc...
I hope that helps?

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);
};

Resources