I need your help.
I have an active google form where customers register.
When I submit the form I have 3 activators that run me scripts, especially they convert the ITALIAN date format (dd-mm-yyyy) to USA (yyyy-mm-dd).
Start of action: Upon submitting the form
The problem I find is this.
These scripts do not always work even if from the control panel I find that it has been executed correctly without reporting errors.
A code example:
function respondToFormSubmit() {
var ss = SpreadsheetApp.openById("xxxxxxxxxxxxxxxxxxxxxxxx");
var sheet = ss.getSheets()[0];
// Format column I
var column1 = sheet.getRange("F:F");
var column2 = sheet.getRange("J:J");
var column3 = sheet.getRange("K:K");
var column4 = sheet.getRange("A:A");
// Set new date format on column I
column1.setNumberFormat('yyyy-mm-dd');
column2.setNumberFormat('yyyy-mm-dd');
column3.setNumberFormat('yyyy-mm-dd');
column4.setNumberFormat('yyyy-mm-dd');
};
When the problem occurs, all 3 triggers fail.
The non-functioning occurs 20/30% of the time and this discontinuous "error" does not make me understand what the problem is.
Do you have any suggestions for me?
Thank you so much for your invaluable help.
Mauro
The problem with your script running on trigger are likely propagation
issues
It takes some time for a new form response to be inserted into the
spreadsheet, so your number formatting functionality might be run
before the new row gets inserted.
One thing you can do is implement some waiting time at the beginning
at the function, e.g. with
sleep().
However, if you bind your script to the destination spreadsheet
instead of the form itself - you will be able to use the Google
Sheets event
objects
for Form submit which include range.
Sample usage:
function respondToFormSubmit(e) {
var row = e.range.getRow();
var sheet = e.range.getSheet();
sheet.getRange("F" + row).setNumberFormat('yyyy-mm-dd');
sheet.getRange("J" + row).setNumberFormat('yyyy-mm-dd');
sheet.getRange("K" + row).setNumberFormat('yyyy-mm-dd');
sheet.getRange("A" + row).setNumberFormat('yyyy-mm-dd');
};
Related
I have a public spreadsheet, where people can enter their names under the column "Name". Everything in this sheet is protected, except for the cells in the column "Name". Since the spreadsheet is public, I want to avoid a situation where someone can troll and delete all the names that have been inputted. Hence, I'm trying to set up a script using the on edit triggers to protect the cell in this range after anyone has entered their name in a cell. So far I've been manually protecting the cells after a name has been entered.
I've found out that the best way to do this would be to use the on edit trigger. I have used javascript before but as I'm new to google spreadsheet scrips, I can't get my script to run like it's supposed to. The current script is supposed to automatically protect the range on edit, and add a description of when the protection was done.
Sample spreadsheet with the script in it here: https://docs.google.com/spreadsheets/d/18NlVKcaeyOkgqIa6WAuDsu5TSYK37m_vXLmp7p7Q8kc/edit?usp=sharing
function protectOnEdit(event) {
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('Sheet1!A2:A1000');
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var description = 'Protected on ' + stringDate;
var protection = range.protect().setDescription(description);
// below code taken directly from Google's documentation
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
Reference: https://developers.google.com/apps-script/reference/spreadsheet/range#protect()
The data range in question is A2:A1000 and currently it -seems- to partially work, however, it protects the WHOLE range after editing a single cell, instead of just protecting the edited cell like it's supposed to.
Are there any steps I'm missing in order for the script to lock the cells individually, instead of the whole range? Any insights are very appreciated!
I have made some corrections:
function protectOnEdit(e) {
var range = e.range;
// Be sure to have edited the sheet "Sheet1"
if (range.getSheet().getName() != "Sheet1") return;
// Be sure to have a single cell as edited range
if (range.getWidth() != 1 || range.getHeight() != 1) return;
// Be sure to have edited cell inside A2:A1000
if (range.getColumn() != 1 || range.getRow() < 2) return;
// Be sure to have non-blank new value
if (!e.value) return;
// Protect this range
var timeZone = Session.getScriptTimeZone();
var stringDate = Utilities.formatDate(new Date(), timeZone, 'dd/MM/yy HH:mm');
var description = 'Protected on ' + stringDate;
var protection = range.protect().setDescription(description);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) protection.setDomainEdit(false);
}
At first the script checks several conditions for desired protection. They seem to me to be important for the task. If all of them are TRUE, then protects a single cell. The spreadsheet owner has no restrictions to edit protected cells but other editors have.
Of course, the owner should install a trigger for this function properly to automate the process.
I have a page where my team could enter data into one page and then hit a save button, then it would copy and paste the data into a database file then clear the contents of the original page.
What I'm trying to figure out is how I can hard stop the save if the pasted information is incorrect.
example. they copied the incorrect information from the one program we use and paste it into the spreadsheet. but the customer number is in the wrong cell.
This would skew the information in the database.
So my thoughts were to have a criteria check of some sort. So it would check the data in a specific column of cells and check to see if it were between 10000 and 99999 or check to ensure it has 5 digits. then have a second check for something similar.
It would be better if I can find a way to have a Paste button that could paste the information and have the hard stop within that code. But baby steps...
function Copy() {
var sss = SpreadsheetApp.openById('file name here');
var ss = sss.getSheetByName('InputData');
var range2 = sss.getRange('D8:O1000');
var range = ss.getRange('A8:Q1000');
var data = range.getValues();
var tss = SpreadsheetApp.openById('file name here');
var ts = tss.getSheetByName('OutOfStockData');
ts.getRange(ts.getLastRow()+1, 1, data.length, data[0].length).setValues(data);
range2.clearContent();
}
Any help would be greatly apricated.
Thanks.
EDIT.
I'm unable to use forms as we copy the information from an internal WMS program we use. When we copy the information, it gets copied in a format that is recognized by any spreadsheet. So we would just copy and paste into the spreadsheet and then add any extra information needed to each line in the extra columns. (ie. comment column.)
The checks needed is so I can get accurate information on Stockouts. Sometimes an error can be made when one of my teammates copies information from a different window in WMS. so one check like mentioned before would be customer number which is always in column J. and never more than 99999 or less then 10000. another check could be the Case ID which is always in column D which is never more then 9999999 and less then 1000000. verifying these two columns could prevent the data from being skewed if the wrong information is copied from WMS.
So the Information gets Copied from WMS, Pasted into InputData, then when the added information gets entered I would click the save button and it would take all the data in the inputData sheet and copy it into the outofstockdata sheet where It would be used for other metric data needed.
As far as the Paste button, Some of the guys who would need to use this sheet are not very computer literate. So the goal would be to have them press said Paste button and the information from our WMS program which would be on the clipboard within windows(no different than copying and pasting from an email to a word document). which we have copied earlier be pasted into the Frist sheet where we would comment.
Thanks.
To hard stop an script that met a condition use return. This could be shown with vanilla JavaScript
function myFunction(){
var upperLimit = 10;
var lowerLimit = 5;
var input = prompt('Give me a value');
// This is the condition that will hard stop the script.
var condition = input >= lowerLimit && input <= upperLimit;
if (condition){
return; // This hard stop the script.
} else {
//if the condition is not met, continue
myFunction();
}
}
myFunction();
So I'll start out by saying I just started learning Javascript 4 days ago. Now that that's out of the way, my intention with this script.
I'd like to automate the process of moving Google Form Responses, which are collected in a spreadsheet, to a new sheet within the same workbook as an archive.
I'd like this to happen on a weekly basis, and for each archive sheet that is created to have only 1 weeks responses. This should be between 12:01AM-1:00AM on Sundays, it really doesn't matter during that hour when it happens.
I would also like to then delete all of those responses from the primary collection sheet(Current_Responses), but if I have to manually delete these later it's fine (and probably good, because then I can review that the script worked properly).
I feel like I have a pretty solid start on doing this, but since I am new to all this, I would really appreciate it if a more experienced scripter could look over my code and tell me if this will work how I intend it to, and if not, where the mistakes are and how to correct them. I'm happy to make mistakes, and then learn from them so any advice will be deeply honored.
I researched several topics and scripts across three websites to help put this together. Thanks in advance for any help and advice!
// function to copy from Current_Responses to new sheet 'Archived_Responses
//(UTC Date)' placed after Current_Responses
function CreateCopySheetWeekly() {
//source info
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName('Current_Responses');
var range = ss.getRange ('A:I'); //replace column length as needed
var data = range.getValues ();
//creates target sheet to copy responses to
var ts = 'Archived_Responses '+formatDate();
ss.insertSheet(sheetName, ss.getSheets().length, {template: templateSheet});
ts.getRange(ts.getLastRow()+1, 1, data.length, data[0].length).setValues(data);
}
//end of primary function
//function to determine and format UTC Date for CreateCopySheetWeekly function
function formatDate() {
var month, day, d = new Date();
month = ('0'+(d.getUTCMonth()+1)).slice(-2);
day = ('0'+(d.getUTCDate()).slice(-2);
return d.getUTCFullYear()+'-'+month+'-'+day;
}
//end of date function
//check every hour to determine when to perform newSheetLast function. Intended for Sunday
//between 0001-0100
window.setInterval (onSunday(){
var today = new Date();
if (today.getDay() == 0 && today.getHours() === 12) {
CreateCopySheetWeekly();
}, 600000);
Go easy on me since I am new at this, but constructive criticism never hurt anyone.
If you run this once a week we can make the simplifying assumption that you want all responses backed up once a week and then clear the main response sheet.
Let's go through the functions step by step and optimize it.
First of all you are already getting all rows, if you want all data you can just getDataRange() and not have to worry about extending.
Also we won't need the actual data.
function CreateCopySheetWeekly() {
//source info
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName('Current_Responses'); // Current Responses
var range = ss.getDataRange();
formatDate() is just creating a ISO8601 representation of the current date if I understand correctly so you can remove the function and instead use
var ts = 'Archived_Responses '+ new Date().toISOString().slice(0,10);
Insert sheet with a template already copies all the data so you only need
ss.insertSheet(ts, ss.getSheets().length, {template: templateSheet});
Then we want to clean up our main response sheet.
If we just clear the range the form will keep appending after what would be the last row if we had never cleared because it remembers how many responses it got so we need to delete all rows but the headers.
templateSheet.deleteRows(2, range.getNumRows() - 1);
Making the final script
function CreateCopySheetWeekly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName('Current_Responses');
var range = ss.getDataRange();
var ts = 'Archived_Responses '+ new Date().toISOString().slice(0,10);
ss.insertSheet(ts, ss.getSheets().length, {template: templateSheet});
templateSheet.deleteRows(2, range.getNumRows() - 1);
}
Lastly you can schedule it by going to Resources > Current Project's Triggers and set up a time based trigger.
I'm pulling my hair out a little trying to write a conditional Google apps script for Google sheets. The sheet has 5 tabs. Basically, the sheet gets it's data from a google form. Clients fill the form out and it populates our "Master" tab of the sheet in question.
Here's where I'm hung up:
The form is used for teachers to refer students to specific administrators. Whoever is controlling the form needs to be able to assign a specific row of data to a specific administrator, which each have their own tab set up. They do this by selecting, from a drop down, which admin they want to assign the row to.
So, if row 2 is assigned to "Miranda," Certain data would get moved over to the "Miranda" tab. For the sake of argument lets say column D and E of that particular row would get moved over to column A of the "Miranda" tab.
Here's what I've come up with so far:
function importStudent() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var master_sheet = ss.getSheetByName("Master");
var miranda_sheet = ss.getSheetByName("Miranda");
var columnT = master_sheet.getRange("T1:T").getValues();
var columnD = master_sheet.getRange("D1:D").getValues();
var columnE = master_sheet.getRange("E1:E").getValues();
for(var i = 0; i <= columnT; i++) {
if (columnT[i][0] === "Miranda") {
var miranda_row = (miranda_sheet.getLastRow() + 1);
var active_row = SpreadsheetApp.setActiveRange(columnT[i].getRow());
var selectionFirst = columnD.setActiveRange(active_row:columnD);
var selectionLast = columnE.setActiveRange(active_row:columnE);
var fullName = selectionFirst + " " + selectionLast;
fullName.copyValuesToRange(miranda_sheet, 1, 1, miranda_row, miranda_row);
}
}
}
The below code is broken, and i'm looking for a little guidance in making it work. Right off, I know I have at least two issues: 1 there are too many variables, and 2 I'm not sure I can pass a variable as a parameter for the setActiveRange() class. So, my questions:
How can I clean this up? And what (class?) should I be using in order to make it functional?
PS - I still consider myself a novice when it comes to JavaScript. I have the knowledge, but the practical application is something I'm still learning :)
not sure if you need to use script to do what you are trying. Why not use a query formula?
For example:
=query(MasterData!:A:E,"SELECT D,E WHERE A = 'Miranda'",0)
There is some help on queries here. You can also query across spreadsheets. That is to say you could assign Miranda her own spreadsheet, which feeds from your master data.
Please share with me a sample spreadsheet data if you need more assistance.
First off, I know a little bit of Javascript but I have no clue to where I would even start on this. I did lots of searching but didn't turn up anything close to what I want. I have a daily task list in google sheets and I want to duplicate each sheet with the next days date when I right click and choose duplicate. Is this even possible? Can it be done in another way?
It is impossible to change what the "duplicate" item of the context menu does. But one can use the following script to provide an alternative way of duplication, via the menu. It adds a new menu item "Custom > New Day" every time the spreadsheet is opened. Invoking this item will duplicate the current sheet with an incremented date as a name, provided that the name follows the ISO format yyyy-mm-dd.
General reference: starting with Google Apps Script.
function duplicate() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var sheetDate = new Date(sheet.getName() + 'T00:00:00.000Z');
if (!sheetDate.valueOf()) {
Browser.msgBox('Did not recognize "' + sheet.getName() + '" as a date. Use the format yyyy-mm-dd.');
}
else {
sheetDate.setDate(sheetDate.getDate() + 1);
var newSheetName = sheetDate.toISOString().slice(0,10);
ss.insertSheet(newSheetName, {template: sheet});
}
}
function onOpen() {
SpreadsheetApp.getActiveSpreadsheet().addMenu("Custom", [{name: "New Day", functionName: "duplicate"}]);
}
The bit with + 'T00:00:00.000Z' is there because the implementation of date parsing in Google Apps Script is peculiar: it requires the time component, including milliseconds.