spreadsheet script running with trigger and manually - google-apps-script

This should be simple but I'm stuck on this script...
It has a function (createnewsheet) that runs manually AND on a time trigger so I had to choose openById() to access the spreadsheet but when I'm looking at the sheet and run the function manually I want to set the newly created sheet active and that's what is causing me trouble.
When I run the function (createnewsheet) from the script editor everything is fine but when I call it from the spreadsheet menu I get this error message : Specified sheet must be part of the spreadsheet because of the last line of code. AFAIK I'm not addressing a sheet outside my spreadsheet... Any idea what I'm doing wrong in this context ?
Here is a simplified code that shows the problem, a shared copy is available here
var ss = SpreadsheetApp.openById('0AnqSFd3iikE3dGVtYk5hWUZVUFNZMzAteE9DOUZwaVE');
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('master')
var logsheet = ss.getSheetByName('logger')
var FUS1=new Date().toString().substr(25,6)+":00";
function onOpen() {
var menuEntries = [ {name: "Manual test", functionName: "createnewsheet"},
];
sheet.addMenu("Utilities",menuEntries);
SpreadsheetApp.setActiveSheet(logsheet);// this is working fine
}
function createnewsheet(){
var sheetName = "Control on "+ Utilities.formatDate(new Date(), FUS1, "MMM-dd-yy")
try{
var newsheet = ss.insertSheet(sheetName,2);// creates the new sheet in 3rd position
}catch(error){
FUS1=new Date().toString().substr(25,6)+":00";
var newsheet = ss.insertSheet(sheetName+' - '+Utilities.formatDate(new Date(), FUS1, "HH:mm:ss"),2);// creates the new sheet with a different name if already there
}
newsheet.getRange(1,1).setValue(sheetName);
SpreadsheetApp.setActiveSheet(ss.getSheets()[2]);// should make 3 rd sheet active but works only when run from script editor
// SpreadsheetApp.setActiveSheet(newsheet);// should have same result but works only when run from script editor
}

I found a practical workaround to solve my use case : I use a different function from the menu (in wich I setActive() the sheet I want) and call the main function from this one.
When called from the trigger there is no use to set any active sheet so I removed this part from the main function.
It goes like this :
function manualTest(){ // from the ss menu
createnewsheet();
var sheet = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(sheet.getSheets()[2]);// this works from the menu when ss is open
}
function createnewsheet(){ // from the trigger and from function manualTest()
var sheetName = "Control on "+ Utilities.formatDate(new Date(), FUS1, "MMM-dd-yy");
try{
var newsheet = ss.insertSheet(sheetName,2);
}catch(error){
FUS1=new Date().toString().substr(25,6)+":00";
var newsheet = ss.insertSheet(sheetName+' - '+Utilities.formatDate(new Date(), FUS1, "HH:mm:ss"),2);
}
newsheet.getRange(1,1).setValue(sheetName);
}

There is a catch here
SpreadsheetApp.setActiveSheet(ss.getSheets()[2]);
When you run it from script editor, It automatically gets the container spreadsheet and makes the said sheet active, but it is not the case when you run it from Custom Menu or triggers.
Modify this statement to:
ss.setActiveSheet(ss.getSheets()[2]);
ss.setActiveSheet(newsheet);

The best solution i have found is to reseed sheet. getActiveSpreadsheet() recognises the Spreadsheet currently in use so no need to openById once more, but
// SpreadsheetApp.setActiveSheet(ss.getSheets()[2]);
// SpreadsheetApp.setActiveSheet(newsheet);
sheet = SpreadsheetApp.getActiveSpreadsheet(); // reloads the active sheet (including changes to the Sheets array)
sheet.setActiveSheet(sheet.getSheets()[2]); // works as expected from editor and custom menu
I think this suggests that the original ss and sheet have cached the Spreadsheet to memory and their pointer isn't refreshed to reflect structural changes. I tried .flush() as a shortcut, but that seems a one way refresh to the sheet from the script, not the other way around.

Related

How can I duplicate and rename a spreadsheet using a Script?

I have this script which I found here which mimicks the 'make a copy' function, but also renames the sheet all within a single action.
function onOpen() {
var menu = [{name: "Duplicate and name", functionName: "dupName"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu("Custom", menu);
}
function dupName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var name = Browser.inputBox('Enter new sheet name');
ss.insertSheet(name, {template: sheet});
}
I want to take this script a step farther and use it to increase the number in the name of the previous sheet, by 1.
For example, A sheet will be called 'Sheet 1' and when I duplicate the sheet using this script, I want the new one to be called 'Sheet 2'.
Is this possible?
If so, how can I modify this script to do that?
Or is there a better way to write it in the first place?
(considering the post I'm referencing to is 4 years old).
The following script can duplicate the sheets and also rename the same with your custom name and add protection to either selected range or complete sheet as defined in the protection line:
function script() {
var spreadsheet = SpreadsheetApp.getActive();
var myValue = SpreadsheetApp.getActiveSheet().getSheetName();
spreadsheet.duplicateActiveSheet();
var totalSheets = countSheets(); //script function
myValue = "CUSTOMTEXT" + totalSheets; //sample CUSTOMTEXT1
SpreadsheetApp.getActiveSpreadsheet().renameActiveSheet(myValue);
var protection = spreadsheet.getActiveSheet().protect();
protection.setUnprotectedRanges([spreadsheet.getRange('C2:E5')])
.removeEditors(['user1#gmail.com', 'user2#gmail.com']);
protection.addEditor('user0#gmail.com');
SpreadsheetApp.flush();
};
function countSheets() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets().length;
}
If the same function is triggered using the doget(e) function you can be the owner of the sheet irrespective to the triggering user. Kindly follow the following for more details on it: Changing Owner of the Sheet irrespective of the duplicator
You can use this script:
function onOpen() {
var menu = [{name: "Duplicate and name", functionName: "dupName"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu("Custom", menu);
}
function dupName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var array = sheet.getName().split(" ");
var ssnum = Number(array[array.length-1]);
array[array.length-1] = ++ssnum;
ss.insertSheet(array.join(' '), {template: sheet});
}
Note that you need to have a space delimiter between the text part and the number part of the sheet name in order to use this script.
Sample Spreadsheet:

Edit script to include function to change cell color when text changes

I am managing a master document for team rosters. Currently I have this script that adds changes from a different roster document to the master roster document. I would like to note any changes to the master doc with colored cells.
The master sheet runs the script to sync the data from the source doc (other roster doc) to the target doc (master roster doc). I want to make it so that any changes to the master doc get highlighted (changed cell background color).
For this specific project, change is defined by additions to the doc (new rows of text) AND changes to existing data (existing rows changing text).
I'm not entirely sure how to implement onEdit triggers. I have read solutions to other problems (i.e. changing the color when the value changes to another specific value) but I haven't found a script that adds the color in when a change is made in general (any change at all, doesn't have to match a specific value).
// create menu buttons
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [{
name: "Sync Spreadsheet Data",
functionName: "copyDataToWorkingSpreadsheet"
}];
ss.addMenu("Data Update Functions", menuEntries);
};
// copy data from form sheet to live mapping sheet
function copyDataToWorkingSpreadsheet() {
// source doc
var sss = SpreadsheetApp.openById('Source Doc');
// source sheet
var ss = sss.getSheetByName('teams');
// source sheet
var ss = sss.getSheetByName('players');
// Get full range of data
var SRange = ss.getDataRange();
// get A1 notation identifying the range
var A1Range = SRange.getA1Notation();
// get the data values in range
var SData = SRange.getValues();
// target spreadsheet
var tss = SpreadsheetApp.openById('Target Doc');
// target sheet
var ts = tss.getSheetByName('teams');
// target sheet
var ts = tss.getSheetByName('players');
// set the target range to the values of the source data
ts.getRange(A1Range).setValues(SData);
};
Right now the script works just like I need it to. Any help implementing the color change is much appreciate!
Something like this would probably work for you.
function trackChanges(e) {
var rg=e.range;
var sh=rg.getSheet();
if(sh.getName()=='players'){
var row=e.range.rowStart;
var col=e.range.columnStart;
sh.getRange(row,col).setBackground('#ffff00');//yellow highlight changes
}
}
Please note: You cannot run a function like this from the script editor or menu (without supplying the event obj as described here).

hideSheet() being executed, but not actually hiding the sheet

I am running this function i made in Google Apps Script for a spreadsheet add-on. It does everything right, but every OTHER execution it doesn't hide the sheet. Any ideas?
function addEmailNew(formObject) {
var newEmail = formObject.addEmailText;
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.getSheetByName("No Touching!").activate();
//find the last string value in a column
var Avals = ss.getRange("J:J").getValues();
var Alast = Avals.filter(String).length + 1;
ss.getSheetByName('No Touching!').getRange("J" + Alast).setValue(newEmail);
//THIS IS THE THING THAT WORKS EVERY OTHER TIME
SpreadsheetApp.getActiveSheet().hideSheet();
openDialog();
return Logger.log("this did stuff");
}
It's fine to get the active sheet, but prior to hide it, activate another sheet. Try something like the following:
var sheetToHide = SpreadsheetApp.getActiveSheet();
ss.getSheetByName("Other sheet").activate();
sheetToHide.hideSheet();
If the problem persists, add SpreadsheetApp.flush(); on a new line after hideSheet().

Time trigger doesn't work with CopyTo in Google Spreadsheet

I've this code in a google spreadsheet which works well when I run it with an interactive execution:
var originalSpreadsheet = SpreadsheetApp.getActive();
var original = originalSpreadsheet.getSheetByName("Sheet1");
var newSpreadsheet = SpreadsheetApp.create("New");
original.copyTo(newSpreadsheet);
But if I run it with a time trigger it creates correctly the "New" spreadsheet, but the copyTo command doesn't copy anything of the original spreadsheet in it.
Have I made some mistake?
I use Mac OS X Lion 10.7.5 and Chrome.
As mentioned in the comment above the sheet copy has a new name because Sheet1 exists by default when you create the spreadsheet. You can avoid this with a small change in your code. See code below
function test(){
var originalSpreadsheet = SpreadsheetApp.getActive();
var original = originalSpreadsheet.getSheetByName("Sheet1");
var newSpreadsheet = SpreadsheetApp.create("New");
var defaultSheet = newSpreadsheet.getSheetByName('Sheet1').setName('xxx');
original.copyTo(newSpreadsheet).setName('Sheet1');
newSpreadsheet.deleteSheet(defaultSheet);
}
or, more simply, you can also copy the spreadsheet itself directly (if you want to copy the whole content of the original spreadsheet)
function test(){
var originalSpreadsheet = SpreadsheetApp.getActive();
var newSpreadsheet = originalSpreadsheet.copy('new')
}
This option will copy the script as well, the copy is a "clone" of the original.

TypeError: Cannot find function getCell in object Sheet

I am completely new to coding anything related to Google Apps Script and JavaScript in general.
I've adapted a script to my needs, but I am getting the following error when I run it:
TypeError: Cannot find function getCell in object Sheet
Essentially, I am trying to get the value in cell D4 (4,4) and pass that value to the variable emailTo. I'm obviously not doing it correctly. The rest of the script should work fine. Any guidance is appreciated.
// Sends PDF receipt
// Based on script by ixhd at https://gist.github.com/ixhd/3660885
// Load a menu item called "Receipt" with a submenu item called "E-mail Receipt"
// Running this, sends the currently open sheet, as a PDF attachment
function onOpen() {
var submenu = [{name:"E-mail Receipt", functionName:"exportSomeSheets"}];
SpreadsheetApp.getActiveSpreadsheet().addMenu('Receipt', submenu);
}
function exportSomeSheets() {
// Set the Active Spreadsheet so we don't forget
var originalSpreadsheet = SpreadsheetApp.getActive();
// Set the message to attach to the email.
var message = "Thank you for attending ! Please find your receipt attached.";
// Construct the Subject Line
var subject = "Receipt";
// THIS IS WHERE THE PROBLEM IS
// Pull e-mail address from D4 to send receipt to
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var emailTo = sheet.getCell(4, 4).getValue();
// Create a new Spreadsheet and copy the current sheet into it.
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export");
var projectname = SpreadsheetApp.getActiveSpreadsheet();
sheet = originalSpreadsheet.getActiveSheet();
sheet.copyTo(newSpreadsheet);
// Find and delete the default "Sheet 1"
newSpreadsheet.getSheetByName('Sheet1').activate();
newSpreadsheet.deleteActiveSheet();
// Make the PDF called "Receipt.pdf"
var pdf = DocsList.getFileById(newSpreadsheet.getId()).getAs('application/pdf').getBytes();
var attach = {fileName:'Receipt.pdf',content:pdf, mimeType:'application/pdf'};
// Send the constructed email
MailApp.sendEmail(emailTo, subject, message, {attachments:[attach]});
// Delete the wasted sheet
DocsList.getFileById(newSpreadsheet.getId()).setTrashed(true);
}
The issue is that getCell() is a method of Range, not Sheet. Get a Range from the Sheet, then use getCell() on the Range object