I am trying to build a function that formats certain columns of a tab (a sheet within the main sheet) that is titled "Responses - DO NOT EDIT." However, every time I run the script from the menu, I receive the error message: "Script function not found: FormatCWR."
Here's the code, that I've frankensteined...
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var entries = [{name:"FormatCWR", functionName:"FormatCWR"}];
ss.addMenu("Scripts", entries);
FormatCRW()
}
function FormatCRW() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetCWR = ss.getSheetByName("Responses - DO NOT EDIT");
var data = sheetCWR.getDataRange().getValues();
var newData = new Array();
for(i in data){
sheetCWR.getRange('H2:H').setNumberFormat("mm/dd/yyyy");
sheetCWR.getRange('J2:J').setNumberFormat('$0.00');
sheetCWR.getRange('K2:K').setNumberFormat('$0.00');
sheetCWR.getRange('R2:R').setNumberFormat('$0.00');
sheetCWR.getRange('BD2:BD').setNumberFormat('$0.00');
sheetCWR.getRange('BG2:BG').setNumberFormat('$0.00');
sheetCWR.getRange('BJ2:BJ').setNumberFormat('$0.00');
sheetCWR.getRange('S2:S').setNumberFormat('[h]:[m]:[s]');
sheetCWR.getRange('T2:T').setNumberFormat('[h]:[m]:[s]');
sheetCWR.getRange('BO2:BO').setNumberFormat('[h]:[m]:[s]');
break;
}
};
I assume the function is in a ".gs" file and that it really is there. So it's possible that something is wrong somewhere else. I know a lot people make their menus in a manner that is similar to what you're doing. However, I like doing it this way.
SpreadsheetApp.getUi().createMenu('Scripts')
.addItem('Format','FormatCRW')
.addToUi();
Maybe this will help...Maybe not.
I'm probably missing something here but this function makes no sense to me.
function FormatCRW() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetCWR = ss.getSheetByName("Responses - DO NOT EDIT");
var data = sheetCWR.getDataRange().getValues();
var newData = new Array();//Not used at all
for(i in data){//Why are you looping on every row in the data range when all of the ranges cover all of the rows?
sheetCWR.getRange('H2:H').setNumberFormat("mm/dd/yyyy");
sheetCWR.getRange('J2:J').setNumberFormat('$0.00');
sheetCWR.getRange('K2:K').setNumberFormat('$0.00');
sheetCWR.getRange('R2:R').setNumberFormat('$0.00');
sheetCWR.getRange('BD2:BD').setNumberFormat('$0.00');
sheetCWR.getRange('BG2:BG').setNumberFormat('$0.00');
sheetCWR.getRange('BJ2:BJ').setNumberFormat('$0.00');
sheetCWR.getRange('S2:S').setNumberFormat('[h]:[m]:[s]');
sheetCWR.getRange('T2:T').setNumberFormat('[h]:[m]:[s]');
sheetCWR.getRange('BO2:BO').setNumberFormat('[h]:[m]:[s]');
break;//Why have a loop if your going to break out the very first time?
}
};
It would make a little more sense this way
function FormatCRW() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetCWR = ss.getSheetByName("Responses - DO NOT EDIT");
sheetCWR.getRange('H2:H').setNumberFormat("mm/dd/yyyy");
sheetCWR.getRange('J2:J').setNumberFormat('$0.00');
sheetCWR.getRange('K2:K').setNumberFormat('$0.00');
sheetCWR.getRange('R2:R').setNumberFormat('$0.00');
sheetCWR.getRange('BD2:BD').setNumberFormat('$0.00');
sheetCWR.getRange('BG2:BG').setNumberFormat('$0.00');
sheetCWR.getRange('BJ2:BJ').setNumberFormat('$0.00');
sheetCWR.getRange('S2:S').setNumberFormat('[h]:[m]:[s]');
sheetCWR.getRange('T2:T').setNumberFormat('[h]:[m]:[s]');
sheetCWR.getRange('BO2:BO').setNumberFormat('[h]:[m]:[s]');
}
Related
Have found nothing online on this:
I have built a script that repeatedly populates a sheet before copying it to a separate spreadsheet. The issue is my script behaves differently if executed from the editor versus when triggered from inside the spreadsheet.
When run from the Apps Script editor, the script runs as expected whereas when run from a custom menu item or a trigger in an embedded photo, my function populating my original sheet doesn't have a chance to finish execution before it is copied to a separate spreadsheet.
When executed from a trigger, my separate spreadsheet's copied sheet resembles my original sheet mid-execution. It gets copied before the function populating the original sheet is done executing. This is a problem since my code is meant for another person to use it directly from the spreadsheet.
edit (here's the code):
//loop thru supervisor, create new page
for (let i = 0; i < swConcise.length; i++){
let swName = swConcise[i];
//draw social worker report function
batchSocialWorker(swName);
//copy to alt ss
const sheet = baseSS.getSheetByName('SocialWorkersReport');
sheet.copyTo(newSS);
//rename sheet
newSS.getSheets()[i+1].setName(swName);
}
Edit (longer code if you need):
function batchSocialWorkers(){
/*
1. create new ss, set baseSS, newSS vars
2. create directors array
3. loop directors, copy sheet to newSS and rename
4. create link in new sheet
*/
const baseSS = SpreadsheetApp.getActiveSheet();
//create new ss titled date, sw Report
const today = date();
const title = "Social Workers Reports " + today;
const id = createSS (title);
const newSS = SpreadsheetApp.openById(id);
//scrape social workers names
const swAll = baseSS.getSheetByName('Relationships').getRange('F11:F').getValues();
//create concise s0cial workers array
const swConcise = cleanupArray(swAll);
//loop thru supervisor, create new page
for (let i = 0; i < swConcise.length; i++){
let swName = swConcise[i];
//draw social worker report function
batchSocialWorker(swName);
//copy to alt ss
const sheet = baseSS.getSheetByName('SocialWorkersReport');
sheet.copyTo(newSS);
//rename sheet
newSS.getSheets()[i+1].setName(swName);
}
//delete first 2 sheets
newSS.deleteSheet(newSS.getSheets()[0]);
newSS.deleteSheet(newSS.getSheets()[0]);
//provide link to new sheet
const link = 'https://docs.google.com/spreadsheets/d/' + id;
var ui = SpreadsheetApp.getUi();
ui.alert('Batch Social Workers Reports Created', link, ui.ButtonSet.OK);
}
function createSS (title) {
// This code uses the Sheets Advanced Service, but for most use cases
// the built-in method SpreadsheetApp.create() is more appropriate.
try {
let sheet = Sheets.newSpreadsheet();
sheet.properties = Sheets.newSpreadsheetProperties();
sheet.properties.title = title;
const spreadsheet = Sheets.Spreadsheets.create(sheet);
return spreadsheet.spreadsheetId;
} catch (err) {
// TODO (developer) - Handle exception
console.log('Failed with error %s', err.message);
}
}
function cleanupArray (array){
let newArray = new Array();
for (let i = 0; i < array.length; i++){
if (array[i][0] != '') newArray.push(array[i][0]);
}
return newArray;
}
This is partly a guess because you have not provided all of the code:
But try this. I still don't know which functions you are calling by a trigger. But I did see several errors in your code. I tried to repair what I can.
function batchSocialWorkers() {
const bss = SpreadsheetApp.getActive();
const title = "Social Workers Reports " + Utilities.formatDate(new Date(),bss.getSpreadsheetTimeZone(),"MM/dd/yyyy");//guess at the format
const ss = SpreadsheetApp.create(title);
const sh = bss.getSheetByName('Relationships')
const vs = sh.getRange('F11:F' + sh.getLastRow()).getValues();
for (let i = 0; i < vs.length; i++) {
let swName = vs[i];
batchSocialWorker(swName);
const sheet = bss.getSheetByName('SocialWorkersReport');
sheet.copyTo(ss);
ss.getSheets()[i + 1].setName(swName);
}
ss.deleteSheet(ss.getSheets()[0]);
ss.deleteSheet(ss.getSheets()[0]);
const link = 'https://docs.google.com/spreadsheets/d/' + id;
var ui = SpreadsheetApp.getUi();
ui.alert('Batch Social Workers Reports Created', link, ui.ButtonSet.OK);
}
I have this code that I copied/pasted/modified from the Google Scripts help files:
function DHGreen() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("D3:P43");
var rule = SpreadsheetApp.newConditionalFormatRule()
.whenTextEqualTo("DH")
.setBackground("#00ff00")
.setRanges([range])
.build();
var rule2 = SpreadsheetApp.newConditionalFormatRule()
.whenTextEqualTo("Mazie")
.setBackground("#0000ff")
.setRanges([range])
.build();
var rule3 = SpreadsheetApp.newConditionalFormatRule()
.whenTextEqualTo("Herald")
.setBackground("#9900ff")
.setRanges([range])
.build();
var rules = sheet.getConditionalFormatRules();
rules.push(rule);
rules.push(rule2);
rules.push(rule3);
sheet.setConditionalFormatRules(rules);
}
I feel like there should be a way to do this more efficiently, but since I'm still at the copy/paste, change things and hope it still works phase of learning Google Scripts (much more familiar with VBA), I'm just not sure where to start. Any help is appreciated.
You can use a function to create the rule rather than reapeating that code.
function makeRule(range, whenTextEqualTo, setBackground) {
return SpreadsheetApp.newConditionalFormatRule()
.whenTextEqualTo(whenTextEqualTo)
.setBackground(setBackground)
.setRanges([range])
.build();
}
function DHGreen() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getRange("D3:P43");
var rules = sheet.getConditionalFormatRules();
rules.push(makeRule(range, "DH", "#00ff00"));
rules.push(makeRule(range, "Mazie", "#0000ff"));
rules.push(makeRule(range, "Herald", "#9900ff"));
sheet.setConditionalFormatRules(rules);
}
I have a static local webpage that is supposed to be updated with data from a csv file (so as data is written to the file, the page should update and display the new data). My issue is that with many data pointys, it becomes very slow and after a certain number of points it will not update at all.
Currently, the way that the page updates is that it reads the file every 3 seconds and updates accordingly. I imagine a much more efficient way would be to see what are the most recent additions to the file and then just append those new points to the current data set. I'm just not sure how to do that.
Code below parses the csv file and separates the data to arrays to be used in the charts:
function parseCSVData(csvFile) {
time = [];
altitude = [];
outsideTemp = [];
insideTemp = [];
voltage = [];
state = [];
velocity = [];
degrees = [];
// cut CSV dataFiles into lines
var lines = csvFile.split("\n");
$.each(lines, function (lineNumber, line) {
if (lineNumber != 0) { // skip header line
var fields = line.split(",");
var missionTime = parseInt(fields[1]);
var altitude2 = parseInt(fields[2]);
var outsideTemp2 = parseInt(fields[3]);
var insideTemp2 = parseInt(fields[4]);
var voltage2 = parseInt(fields[5]);
var state2 = parseInt(fields[6]);
var velocity2 = parseInt(fields[7]);
var degrees2 = parseInt(fields[8]);
time.push(missionTime);
altitude.push(altitude2);
outsideTemp.push(outsideTemp2);
insideTemp.push(insideTemp2);
voltage.push(voltage2);
state.push(state2);
velocity.push(velocity2);
degrees.push(degrees2);
}
});
}
This is the code to update the charts every 3 seconds:
setInterval(function blah() {
var file = fileName+'?q='+Math.random();
fillCharts(file);//which calls the parseCSVData function and fills the charts
}, 3000);
EDIT: pastebin with entire code: http://pastebin.com/Qmzn8azY
EDIT2: sample csv data:
TEAM_ID,MISSION_TIME,ALT_SENSOR,OUTSIDE_TEMP,INSIDE_TEMP,VOLTAGE,FSW_STATE,VELOCITY,DEGREES
ubArtemis,0,36,20,20,9,1,0,0
ubArtemis,1,45,18,20,9,1,6,2
ubArtemis,2,200,16,20,9,1,10,5
ubArtemis,3,65,14,19,9,1,15,3
ubArtemis,4,79,12,17,8,2,22,4
ubArtemis,5,100,10,16,8,3,30,2
ubArtemis,6,120,8,15,8,4,39,0
I'm trying to write a little script to make my coworkers and mine lives easier. I am trying to append lines to a spreadsheet based on information entered into a custom form. The code posted below just the doPost block which should be appending the google spreadsheet.
function doPost(form) {
var PN = form.PartNumber;
var REV = form.Revision;
var DATE = form.RevisionDate;
var DESC = form.Description;
var NOTE = form.PartNotes;
var URL = form.myFile.getURL();
var ss = SpreadsheetApp.openById("ID HERE"); // removed ID for sake of safety (let me be paranoid)
var sheet = ss.getSheetName('Uploads');
sheet.appendRow([PN,REV,DATE,DESC,NOTE,URL]);
}
I am unsure why it isn't writing to the spreadsheet but it isn't throwing me any errors. If you can offer any insight as to what is wrong I would greatly appreciate it; there are many guides online but most seem to be based on deprecated functions/code/etc.
Thanks for your time.
Instead of using doPost, set up a "On form submit" trigger.
You need to get the namedValues to be able to pull specific values and take the first output.
Also, it should be "getSheetByName('Uploads')" .
As pointed out in the previous answer, it is unclear what you are trying to achieve by "form.myFile.getURL();" If you want to get the form url you might as well create it as a string, as it always stays the same.
Here is a working example of your code:
function doPost(form) {
var formResponses = form.namedValues;
var PN = formResponses.PartNumber[0];
var REV = formResponses.Revision[0];
var DATE = formResponses.RevisionDate[0];
var DESC = formResponses.Description[0];
var NOTE = formResponses.PartNotes[0];
//var URL = form.myFile.getURL(); //Not sure what you are tyring to get here as form URL will always be the same.
var URL = "Your form's url"; //You can put the form url in here so it will be pushed in to every row.
var ss = SpreadsheetApp.openById("ID HERE"); // removed ID for sake of safety (let me be paranoid)
var sheet = ss.getSheetByName('Uploads');
sheet.appendRow([PN,REV,DATE,DESC,NOTE,URL]);
}
The form fields are nested in a "parameter" property in the doPost parameter.
So, you should access them using:
function doPost(form) {
var actualForm = form.parameter;
var PN = actualForm.PartNumber;
//etc
To double check all parameters your receiving and their names, you could append to your sheet everything stringfied, like this:
sheet.appendRow([JSON.stringify(form)]);
--edit
This form.myFile.getURL() also looks odd. I guess another good debugging trick you could do is to wrap everything in a try-catch and email yourself any errors you get. For example:
function doPost(form) {
try {
//all your code
} catch(err) {
MailApp.sendMail('yourself#etc', 'doPost error', err+'\n\n'+JSON.stringify(form));
}
}
On form submit
onFormSubmit works. "doPost" looks wrong.
Simple example:
function Initialize() {
var triggers = ScriptApp.getProjectTriggers();
for(var i in triggers) {
ScriptApp.deleteTrigger(triggers[i]);
}
ScriptApp.newTrigger("SendGoogleForm")
.forSpreadsheet(SpreadsheetApp.getActiveSpreadsheet())
.onFormSubmit()
.create();
}
function SendGoogleForm(e)
{
try
{
Full example - Scroll down to the code http://www.labnol.org/internet/google-docs-email-form/20884/ (Note: example sends email)
Trigger docs: https://developers.google.com/apps-script/guides/triggers/events
Notes: I think the problem is doPost, Does it work with google Forms? Never seen it used with google forms.
First and foremost, thank you everyone who has responded with information thus far. None of the solutions posted here worked for my particular implementation (my implementation is probably to blame, it is very crude), but they definitely set me down the path to a working version of my form which we now lightly use. I have posted some of the code below:
function sheetFill(form, link) {
try {
var formResponses = form.namedValues;
var toForm = [0,0,0,0,0,0,0];
for (i=0;i < form.PartNumber.length;i++){
toForm[0] = toForm[0]+form.PartNumber[i];
}
... (several for loops later)
var d = new Date();
var ss = SpreadsheetApp.openById("IDHERE");
var sheet = ss.getCurrentSheet;
ss.appendRow([toForm[0], toForm[1], toForm[2], toForm[3], toForm[4], toForm[5], toForm[6], link, d]);
} catch(err) {
MailApp.sendEmail('EMAIL', 'doPost error', err+'\n\n'+JSON.stringify(form));
}
}
It is not very versatile or robust and isn't elegant, but it is a starting point.
sorry if this is an elementary question, but I just can't get this to work the way I need.
I have a script that essentially consists of 3 parts:
1). Removes all protection in a sheet
2). Executes some copying functions (since ranges are protected I need to remove the protection first #1)
3). Sets the protection back up after #2 is finished.
Here's my code:
First clears protection
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('COST REPORT');
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
Second clears data in cells
var costReport = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(
'COST REPORT');
costReport.getRange('F12:F16').clearContent(); //Theoreticals
costReport.getRange('D20:D20').clearContent(); //Week Ending Date
Third sets protection
var ss = SpreadsheetApp.getActive().getSheetByName('COST REPORT');
var costReportCOGS = ss.getRange('G11:G16');
var protection = costReportCOGS.protect().setDescription('costReportCOGS');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
var costReportPurchaseEnding = ss.getRange('D11:E16');
var protection = costReportPurchaseEnding.protect().setDescription(
'costReportPurchaseEnding');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
I've cut some of the script down for ease of debugging, but basically I need the script to Execute & Finish in this order, one by one. If you just try running the script the way it is, the protection doesn't get removed and I get the error "trying to edit protected range...."
If I run each block by itself then it works perfect, but that consists of 3 different scripts the user has to run and I need it all in one.
Thanks in advance!
Sean.
Something like this?
function removeProtection() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('COST REPORT');
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
};
function clearRangeData() {
var costReport = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(
'COST REPORT');
costReport.getRange('F12:F16').clearContent(); //Theoreticals
costReport.getRange('D20:D20').clearContent(); //Week Ending Date
};
function weeklyFileRangeProtection() {
//COST REPORT
var ss = SpreadsheetApp.getActive().getSheetByName('COST REPORT');
var costReportCOGS = ss.getRange('G11:G16');
var protection = costReportCOGS.protect().setDescription('costReportCOGS');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
};
You are having issues because for each function you are calling SpreadsheetApp.getActiveSpreadsheet . Each time you make this call you create a virtual "copy" of the spreadhseet, and the changes you make to this copy are only passed to the version in Google's servers once the whole script is finished. Hence, if you manually run each of the 3 function that the workflow:
Run function 1 -> script finished -> update the spreadsheet in the server -> run function 2 (which now gets the updated spreadsheet) -> script finished -> update the spreadsheet in the server -> run function 3 (which now gets the re-updated spreadsheet) -> script finished -> update the spreadsheet in the server
Now, if you run the three functions, the way the script is here is what happens:
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('COST REPORT'); this creates a virtual copy of the spreadsheet -> your code removes the protection from this copy and the server spreadsheet is not modified -> you call again var costReport = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('COST REPORT'); which create a new copy of the server spreadsheet, which hadn't its protections removed yet -> your code tries to clear the data on this copy, which triggers the error.
As #Cameron Roberts suggested in his answer Spreadsheet.flush() between the calls will solve the issue, because if forces the changes to be synced to the spreadsheet in the server. But you will have another "problem", which is the amount of copies you are calling, the .getActiveSpreadsheet() is very time consuming! It is better if you make only one call, store in a variable (you already do that, it is your variable ss) and make all the edits to that.
Your code will end up looking like this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var costReport = ss.getSheetByName('COST REPORT');
//First clear protection
var protections = costReport.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
};
};
//Second clears data in cells
costReport.getRange('F12:F16').clearContent(); //Theoreticals
costReport.getRange('D20:D20').clearContent(); //Week Ending Date
//Third sets protection
var costReportCOGS = costReport.getRange('G11:G16');
var protection = costReportCOGS.protect().setDescription('costReportCOGS');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
};
var costReportPurchaseEnding = costReport.getRange('D11:E16');
var protection = costReportPurchaseEnding.protect().setDescription(
'costReportPurchaseEnding');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
};
This method also applies to Google Docs, which does NOT have a similar .flush() method for updating the server version.
I believe you have misdiagnosed the issue slightly. The code is already running in the correct order, but the protection is simply not being removed before the write calls are executed, due to the nature of Google's underlying architecture.
The comments steering you towards asynchronous behaviour are not helpful in this case, they do make sense from a Javascript perspective but are not the issue here, this is an Apps Script / Google Sheets issue, none of the functions you are calling are asynchronous.
I have two suggestions, one is to try calling SpreadsheetApp.flush() after the protections are removed. The other is to use Utilities.sleep() to artificially pause the script for a brief period after executing the remove() calls.
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app#flush()
https://developers.google.com/apps-script/reference/utilities/utilities#sleep(Integer)