I want to run a Google Script which normalises a cell range, on a number (30+) of Google Sheets. Or more precisely, I want a (less technical) user to be able to do this. I can't seem to find a reasonable workflow.
The options I can see are:
Copy/paste the script as a bound script to each of the spreadsheets
That's messy because then there are many copies of the script, which generally won't be run again, and there's a lot of overhead and clicking around to install the macro for each one.
Use a library
I could put the body of the code in a library, then make the copy/paste just a stub (like the accepted answer here ).
However, that's still just as bad for the UX, plus various reports that libraries are messy to deal with.
Make an add-on
The "right way" seems to be to create an add-on which the user can enable for each spreadsheet. However, add-ons still seem to be in "developer preview" mode, and the authorisation cycle is uncertain and potentially slow. Google also expects that The script has been tested with multiple active users. which would be hard - by the time I'd tested it this thoroughly, the job would basically be done. And how would I test it without publishing the add-on anyway?
Other options?
Is there some other way, perhaps using an unbound-script? It's not possible to run a single script once and have it iterate over all the spreadsheets as a bit of user input is required (which range within the spreadsheet etc).
Is there a way where the user could install an unbound script, run it, and it would ask which spreadsheet to run it on?
openByUrl()
is really close, but it doesn't actually open the spreadsheet UI, so I wouldn't be able to use functions like getActiveRange()
etc.
In case it's relevant, here's the script:
/*function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Normalize')
.addItem('Normalize Crosstab', 'normalizeCrossTab')
.addToUi();
}*/
function onOpen() {
var ss = SpreadsheetApp.getActive();
var items = [
{name: 'Normalize Crosstab', functionName: 'normalizeCrosstab'},
];
ss.addMenu('Normalize', items);
}
/* Converts crosstab format to normalized form. Given columns abcDE, the user puts the cursor somewhere in column D.
The result is a new sheet, NormalizedResult, like this:
a b c Field Value
a1 b1 c1 D D1
a1 b1 c1 E E1
a2 b2 c2 D D2
a2 b2 c2 E E2
...
*/
function normalizeCrosstab() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var firstDataCol = SpreadsheetApp.getActiveRange().getColumn();
var dataCols = values[0].slice(firstDataCol-1);
if (Browser.msgBox("This will create a new sheet, NormalizedResult. Place your cursor is in the first data column.\\n\\n" +
"These will be your data columns: " + dataCols,Browser.Buttons.OK_CANCEL) == "cancel") {
return;
}
var resultssheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("NormalizedResult");
if (resultssheet != null) {
SpreadsheetApp.getActive().deleteSheet(resultssheet);
}
var header = values[0].slice(0, firstDataCol - 1);
var newRows = [];
header.push("Field");
header.push("Value");
newRows.push(header);
for (var i = 1; i <= numRows - 1; i++) {
var row = values[i];
for (var datacol = 0; datacol < dataCols.length; datacol ++) {
newRow = row.slice(0, firstDataCol - 1); // copy repeating portion of each row
newRow.push(values[0][firstDataCol - 1 + datacol]); // field name
newRow.push(values[i][firstDataCol - 1 + datacol]); // field value
newRows.push(newRow);
}
}
var newSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("NormalizedResult");
var r = newSheet.getRange(1,1,newRows.length, header.length);
r.setValues(newRows);
};