EDIT thanks for the assistance, I've updated the script as follows, however it still will not create a submenu from the Add-on menu.
// Hide empty rows in PRINT sheet
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Print Ready');
menu.addItem('Print Ready', 'PRINTREADY');
menu.addToUi();
}
function PRINTREADY(){
var shname='PRINT';//<<<<<<<<<<<<<<<change to appropriate sheet name
var rw1=4, rw2=258;
var s, nrws, data, i;
nrws=rw2-rw1+1;
s=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(shname);
data=s.getRange(rw1, 1, nrws, s.getLastColumn()).getValues();
s.showRows(rw1, nrws);
for (i in data) if (data[i].every(function(x) {return x=='';})) s.hideRows(1*i+rw1);
}
// Sort PANTRY LIST A-Z
var sh = SpreadsheetApp.getActiveSheet();
if( sh.getName() == "PANTRY LIST" ) {
var editedCell = sh.getActiveRange().getColumnIndex();
if(editedCell == 1) {
var range = sh.getRange("PANTRY LIST!A3:Z");
range.sort({column: 1});
}
}
I'm trying to deploy a sheets addon for some restaurant menu costing spreadsheets I have written. For some of the functionality I needed to include scripts, by themselves they are fairly straightforward:
Script 1: HideEmptyRows - this script hides empty rows to make the recipe ready to print, it should only run on a recipe document with a sheet name PRINT.
Script 2: SortList - this script simply sorts the sheet A-Z onOpen and every 15 minutes, it only runs on the Pantry List document with a sheet name PANTRY LIST.
They run fine individually as scripts, but when I combine them into a sheets addon Script 1 doesn't generate a menu, and I get Range Not Found errors from Script 2.
Script 1
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Print Ready')
menu.addItem('Make my recipe ready to print', 'PRINTREADY')
.addToUi();
}
function PRINTREADY(){
var shname='PRINT';//<<<<<<<<<<<<<<<change to appropriate sheet name
var rw1=4, rw2=258;
var s, nrws, data, i;
nrws=rw2-rw1+1;
s=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(shname);
data=s.getRange(rw1, 1, nrws, s.getLastColumn()).getValues();
s.showRows(rw1, nrws);
for (i in data) if (data[i].every(function(x) {return x=='';})) s.hideRows(1*i+rw1);
}
Script 2
function onOpen(){
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var editedCell = sh.getActiveRange().getColumnIndex();
if(editedCell == 1) {
var range = sh.getRange("PANTRY LIST!A3:Z");
range.sort({column: 1});
}
}
Does anyone know how to combine them into one script that I can deploy as an addon?
Thanks
Related
I'm a newbie when it comes to any sort of coding, but I'm trying to figure out a way to fix my spreadsheet. I am working on a Google Sheet for a trucking company where data comes in via a Google Form (e.g. date, driver, amount, pickup, destinaiton, etc) and is a linked to a spreadsheet with responses recorded to a sheet called "Loadboard". Responses start on Row 5.
On "Loadboard", I created a dropdown column called "Paid" in Column R (18) as a place to mark when these orders are completed. The idea is that once "Yes" is selected it will be moved from the "Loadboard" sheet to the "Paid" sheet. I also created a "No" option as part of the dropdown in case we need to move from "Paid" back to "Loadboard" in case this dropdown is selected accidentally.
While I have an onedit function that works great, I'm having trouble figuring out how to convert this to work with data submitted via form (in particular how to write the new onformsubmit function and how to setup the form submission trigger)
Here's the function I'm using:
function onEdit(e) {
let r = e.range;
if (r.columnStart != 18 || r.rowStart == 1 || e.value == null) return;
const sh = SpreadsheetApp.getActive();
const valArray = ["No", "Yes"];
const destArray = ["Loadboard", "Paid"];
let dest = sh.getSheetByName(destArray[valArray.indexOf(e.value)]);
let src = sh.getActiveSheet();
if (dest.getName() == src.getName()) return;
src.getRange(r.rowStart, 1, 1, 18).moveTo(dest.getRange(dest.getLastRow() + 1, 1, 1, 18));
src.deleteRow(r.rowStart);
}
After searching for hours on the internet, this was the closest I got -
function formSubmission() {
var s = SpreadsheetApp.getActiveSheet();
var data = range.getValues();
var numCol = range.getLastColumn();
var row = s.getActiveRow;
var targetinfo = s.getRange(row, (18).getValue);
if (targetinfo() == "Yes") {
var targetSheet = ss.getSheetByName("Paid");
var targetrow = targetSheet.getLastrow() + 1);
var Targetcol = numCol();
targetSheet.getRange(targetrow, 1, 1, Targetcol).setValues(data);
}
}
I'm also using the following trigger setup:
Function to Run: OnEdit
Event Source: From Spreadsheet
Event Type: On Change
Unfortunately, neither seem to be working. I also just tried replacing the original onedit with "formsubmission" which did not work.
I would have to do the same thing, I would let a sheet with all the results untouched. I would make a namedRange with it and two sheets Paid and Loadboard with a request like =QUERY(myNamedRange, "select * where R='Yes'") so get a clean sheet with every "Paid" row.
About your code, a hint : I can't find any var row s.getActiveRow function and i it existed it would be s.getActiveRow() (parenthesis).
I have a script which I have created in Google drive as I want it to be a standalone script to that of the spreadsheet - mainly because other users have been fidling with the scripts and the sheet didn't function properly until i found all the problems!!
One of the scripts is this:
var SORT_COLUMN_INDEX = 2;
var ASCENDING = true;
var NUMBER_OF_HEADER_ROWS = 2;
var activeSheet;
function autoSort(sheet) {
var range = sheet.getDataRange();
if (NUMBER_OF_HEADER_ROWS > 0) {
range = range.offset(NUMBER_OF_HEADER_ROWS, 0);
}
range.sort( {
column: SORT_COLUMN_INDEX,
ascending: ASCENDING
} );
}
function onClickButton() {
var sheet = SpreadsheetApp.getActiveSheet();
autoSort(sheet);
}
I have found this online which apparently links the app script to the spreadsheet but this doesn't work for me... var ss = SpreadsheetApp.OpenFileById(xxx__xxx)
(xxx__xxx) being the spreadsheet id taken as: https://docs.google.com/spreadsheets/d/xxx__xxx/edit#gid=1611726502
This just returns the following when I run the script:
TypeError: Cannot find function OpenFileById in object.
Please could someone assist on where i either put the code var ss = SpreadsheetApp.OpenFileById(xxx__xxx) within the script or the best script to use find the spreadsheet based on the sheet ID?
p.s. i will need to run the function onClickButton if that makes any difference...
Sorting a Spreadsheet from a Standalone Script
var ssid="Spreadsheet Id";
function sortingSS() {
var ss=SpreadsheetApp.openById(ssid);
var sh=ss.getSheetByName('Sheet3');
var rg=sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn());
rg.sort({column:1,ascending:true});
}
Sheet before sort
Sheet after sort
Adding it to a menu
function onOpen() {
SpreadsheetApp.getUi().createMenu("Project Menu")
.addItem("Sorting Spreadsheet", "sortingSS")
.addToUi();
}
Creating a button of your own
I've gotten to the point where I have a pretty solid script that:
1) Takes data from a new data tab and posts it to an existing data tab
2) Clears data from the new data tab
3) Deletes duplicates from the existing data tab
When I originally put this script in, it worked great. But after running it a few times, it seems to stall at the de-duping portion of the script. So when I run it, the first two scripts run, but not the third. If I select the de-dupe script to run on it's own, it works just fine.
Has anyone else seen this issue? Is there any way to tweak the script to have a more reliable run so that it will always process all three scripts?
Not sure how to optimize from there.
function Run(){
insert();
clear1();
removeDuplicates();
}
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName('Candidate Refresh'); // change here
var des = ss.getSheetByName('Candidate Listing'); // change here
var sv = source
.getDataRange()
.getValues();
sv.shift();
des.insertRowsAfter(1, sv.length);
des.getRange(2, 1, sv.length, source.getLastColumn()).setValues(sv);
}
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Candidate Refresh');
sheet.getRange('A2:K100').clearContent()
}
function removeDuplicates() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows=sheet.getLastRow();
var firstColumn=sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function(e){return e[0]})
var uA=[];
for (var i=rows;i>0;i--) {
if (uA.indexOf(firstColumn[i-1])!=-1) {
sheet.deleteRow(i);
}else{
uA.push(firstColumn[i-1]);
}
}
}
All three scripts should fire when the Run script is played.
I'm trying to fill cells with hyperlinks to ranges in Google Sheets app script with the same desired outcome I would get had I done it in GUI. I managed to create hyperlinks to sheet in the form of "gid=..." with the ... being a sheetID. But I struggle to get the rangeID that is used when generating the hyperlink in GUI e.g.
HYPERLINK("#rangeid=1420762593";"'List 4'!F2:F15")
Is it possible to create hyperlinks to ranges in app script?
Yes, you can do this in App Script. Here's a very simple implementation where the HYPERLINK function is built and appended to a cell:
function hyperlinkRange() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Sheet1");
var sheet2 = ss.getSheetByName("Sheet2").getSheetId();
sheet1.getRange("A1").setValue('=hyperlink("#gid='+sheet2+'&range='+sheet1.getRange('A1:A10').getA1Notation()+'", "Click to jump to Sheet 2")');
}
You can combine this with loops to set a value of links across multiple sheets.
Custom functions
Use in a formula.
Simple range:
=HYPERLINK(getLinkByRange("Sheet1","A1"), "Link to A1")
Named range:
=HYPERLINK(getLinkByNamedRange("NamedRange"), "Link to named range")
The code, insert into the script editor (Tools > Script Editor):
function getLinkByRange(sheetName, rangeA1, fileId)
{
// file + sheet
var file = getDafaultFile_(fileId);
var sheet = file.getSheetByName(sheetName);
return getCombinedLink_(rangeA1, sheet.getSheetId(), fileId, file)
}
function getLinkByNamedRange(name, fileId)
{
// file + range + sheet
var file = getDafaultFile_(fileId);
var range = file.getRangeByName(name);
var sheet = range.getSheet();
return getCombinedLink_(range.getA1Notation(), sheet.getSheetId(), fileId, file)
}
function getDafaultFile_(fileId)
{
// get file
var file;
if (fileId) { file = SpreadsheetApp.openById(fileId); }
else file = SpreadsheetApp.getActive();
return file;
}
function getCombinedLink_(rangeA1, sheetId, fileId, file)
{
var externalPart = '';
if (fileId) { externalPart = file.getUrl(); }
return externalPart + '#gid=' + sheetId + 'range=' + rangeA1;
}
Here is another example. Hopefully, it is clean and self-explanatory
function hyperlinkRange(shDest,rgDest,shSrc,rgSrc,linkText) {
// get the spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet()
// get the destination sheet id
var idDest = shDest.getSheetId()
// link the range
var formula = '=hyperlink("#gid='+idDest+'&range='+rgDest+'","'+linkText+'")'
shSrc.getRange(rgSrc).setValue(formula)
}
In case you want to create a link to another sheet which will open the sheet in the same browser tab here is what you want to do:
1. Get the id of the sheet. Check the link in your browser and you will see #gid=x where x is the sheet id
2. Then you want to set the formula (hyperlink) to the cell and make it show as a hyperlink
SpreadsheetApp.getActiveSheet().getRange("A1").setFormula('=HYPERLINK("#gid=X","test")').setShowHyperlink(true);
If you don't use setShowHyperlink(true) it will be shown as a regular text.
I'm opting for batch update of row colors using google apps script. However I cannot go for the usual range function, as the rows to be colored are not consecutive. So I thought, a1 notation would be helpful but unfortunately it looks like I can only pass one range of a1 notation and not multiple:
var a1Notations="A1:C1,A3:C3,A10,C10";
sheet.getRange(a1Notations).setBackground("red");
But I'm getting "Range not found" error.
Any ideas how can I make this work?
Thanks!
To multi-select a number of ranges and change the color:
var sheet = SpreadsheetApp.getActiveSheet();
var rangeList = sheet.getRangeList(['A1:C1','A3:C3','A10','C10']);
sheet.setActiveRangeList(rangeList).setBackground("red");
If you want to type a list in a dialog box:
function promptRangesList() {
var ui = SpreadsheetApp.getUi();
var response = ui.prompt('Comma-separated ranges list', 'E.g.: A1:C1,A3:C3,A10,C10 or C3,C7,C17,C56', ui.ButtonSet.OK_CANCEL);
Logger.log(response.getSelectedButton());
// Process the user's response.
if (response.getSelectedButton() == ui.Button.OK) {
if (response.getResponseText()!=='') {
var list = response.getResponseText().split(',');
Logger.log(list);
var rangeslist = SpreadsheetApp.getActiveSheet().getRangeList(list).setBackground("red");
Logger.log(rangeslist);
rangeslist.activate();
} else {
Logger.log('getResponseText empty');
}
} else if (response.getSelectedButton() == ui.Button.CANCEL) {
Logger.log('CANCELED');
} else {
Logger.log('The user clicked the close button in the dialog\'s title bar.');
}
}
Reference:
Class RangeList - Selects the list of Range instances
Class Sheet - Sets the specified list of ranges as the active ranges in the active sheet.
Prompt dialogs - A prompt is a pre-built dialog box that opens inside a Google Docs, Sheets, or Forms editor.
Put the range notations into an array, then loop through the array:
function setMultiRanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getActiveSheet();
var a1Notations=["A1:C1","A3:C3","A10","C10"];
var i=0,
arryLngth = a1Notations.length;
for (i=0;i<arryLngth;i+=1) {
//Logger.log(a1Notations[i]);
//Logger.log(typeof a1Notations[i]);
sh.getRange(a1Notations[i]).setBackground("red");
};
};
Set each range independently
sheet.getRange("A1:C1").setBackground("red");
sheet.getRange("A3:C3").setBackground("red");
sheet.getRange("A10:C10").setBackground("red");
According to the doc of Google Sheets' API V4 the way to select multiple ranges is to pass an array of ranges.
var ranges = ["'EVERYTHING'!A:A", "'EVERYTHING'!Z:Z"];
// I use Node.js, so my call to the API looks like this:
service.spreadsheets.values.batchGet({
spreadsheetId: spreadsheetId,
ranges: ranges
}, function(err, result) { ...
Then what you get is an array of data, [{first range}, {second range}, {etc...}]