/** * @OnlyCurrentDoc * * The above comment directs Apps Script to limit the scope of file * access for this add-on. It specifies that this add-on will only * attempt to read or modify the files in which the add-on is used, * and not all of the user's files. The authorization request message * presented to users will reflect this limited scope. *//** * A global constant String holding the title of the add-on. This is * used to identify the add-on in the notification emails. */constADDON_TITLE='FormNotifications';/** * A global constant 'notice' text to include with each email * notification. */constNOTICE='FormNotificationswascreatedasansampleadd-on,andis'+'meantfor'+'demonstrationpurposesonly.Itshouldnotbeusedforcomplexorimportant'+'workflows.Thenumberofnotificationsthisadd-onproducesarelimitedbythe'+'owner\'savailableemailquota;itwillnotsendemailnotificationsifthe'+'owner\'sdailyemailquotahasbeenexceeded.Collaboratorsusingthisadd-onon'+'thesameformwillbeabletoadjustthenotificationsettings,butwillnotbe'+'abletodisablethenotificationtriggerssetbyothercollaborators.';/** * Adds a custom menu to the active form to show the add-on sidebar. * * @param {object} e The event parameter for a simple onOpen trigger. To * determine which authorization mode (ScriptApp.AuthMode) the trigger is * running in, inspect e.authMode. */functiononOpen(e){try{FormApp.getUi().createAddonMenu().addItem('Configurenotifications','showSidebar').addItem('About','showAbout').addToUi();}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Runs when the add-on is installed. * * @param {object} e The event parameter for a simple onInstall trigger. To * determine which authorization mode (ScriptApp.AuthMode) the trigger is * running in, inspect e.authMode. (In practice, onInstall triggers always * run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or * AuthMode.NONE). */functiononInstall(e){onOpen(e);}/** * Opens a sidebar in the form containing the add-on's user interface for * configuring the notifications this add-on will produce. */functionshowSidebar(){try{constui=HtmlService.createHtmlOutputFromFile('sidebar').setTitle('FormNotifications');FormApp.getUi().showSidebar(ui);}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Opens a purely-informational dialog in the form explaining details about * this add-on. */functionshowAbout(){try{constui=HtmlService.createHtmlOutputFromFile('about').setWidth(420).setHeight(270);FormApp.getUi().showModalDialog(ui,'AboutFormNotifications');}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Save sidebar settings to this form's Properties, and update the onFormSubmit * trigger as needed. * * @param {Object} settings An Object containing key-value * pairs to store. */functionsaveSettings(settings){try{PropertiesService.getDocumentProperties().setProperties(settings);adjustFormSubmitTrigger();}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Queries the User Properties and adds additional data required to populate * the sidebar UI elements. * * @return {Object} A collection of Property values and * related data used to fill the configuration sidebar. */functiongetSettings(){try{constsettings=PropertiesService.getDocumentProperties().getProperties();// Use a default email if the creator email hasn't been provided yet.if(!settings.creatorEmail){settings.creatorEmail=Session.getEffectiveUser().getEmail();}// Get text field items in the form and compile a list// of their titles and IDs.constform=FormApp.getActiveForm();consttextItems=form.getItems(FormApp.ItemType.TEXT);settings.textItems=[];for(leti=0;i < textItems.length;i++){settings.textItems.push({title:textItems[i].getTitle(),id:textItems[i].getId()});}returnsettings;}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Adjust the onFormSubmit trigger based on user's requests. */functionadjustFormSubmitTrigger(){try{constform=FormApp.getActiveForm();consttriggers=ScriptApp.getUserTriggers(form);constsettings=PropertiesService.getDocumentProperties();consttriggerNeeded=settings.getProperty('creatorNotify')==='true'||settings.getProperty('respondentNotify')==='true';// Create a new trigger if required; delete existing trigger// if it is not needed.letexistingTrigger=null;for(leti=0;i < triggers.length;i++){if(triggers[i].getEventType()===ScriptApp.EventType.ON_FORM_SUBMIT){existingTrigger=triggers[i];break;}}if(triggerNeeded && !existingTrigger){consttrigger=ScriptApp.newTrigger('respondToFormSubmit').forForm(form).onFormSubmit().create();}elseif(!triggerNeeded && existingTrigger){ScriptApp.deleteTrigger(existingTrigger);}}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Responds to a form submission event if an onFormSubmit trigger has been * enabled. * * @param {Object} e The event parameter created by a form * submission; see * https://developers.google.com/apps-script/understanding_events */functionrespondToFormSubmit(e){try{constsettings=PropertiesService.getDocumentProperties();constauthInfo=ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);// Check if the actions of the trigger require authorizations that have not// been supplied yet -- if so, warn the active user via email (if possible).// This check is required when using triggers with add-ons to maintain// functional triggers.if(authInfo.getAuthorizationStatus()===ScriptApp.AuthorizationStatus.REQUIRED){// Re-authorization is required. In this case, the user needs to be alerted// that they need to reauthorize; the normal trigger action is not// conducted, since authorization needs to be provided first. Send at// most one 'Authorization Required' email a day, to avoid spamming users// of the add-on.sendReauthorizationRequest();}else{// All required authorizations have been granted, so continue to respond to// the trigger event.// Check if the form creator needs to be notified; if so, construct and// send the notification.if(settings.getProperty('creatorNotify')==='true'){sendCreatorNotification();}// Check if the form respondent needs to be notified; if so, construct and// send the notification. Be sure to respect the remaining email quota.if(settings.getProperty('respondentNotify')==='true'&&
MailApp.getRemainingDailyQuota() > 0){sendRespondentNotification(e.response);}}}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Called when the user needs to reauthorize. Sends the user of the * add-on an email explaining the need to reauthorize and provides * a link for the user to do so. Capped to send at most one email * a day to prevent spamming the users of the add-on. */functionsendReauthorizationRequest(){try{constsettings=PropertiesService.getDocumentProperties();constauthInfo=ScriptApp.getAuthorizationInfo(ScriptApp.AuthMode.FULL);constlastAuthEmailDate=settings.getProperty('lastAuthEmailDate');consttoday=newDate().toDateString();if(lastAuthEmailDate!==today){if(MailApp.getRemainingDailyQuota() > 0){consttemplate=HtmlService.createTemplateFromFile('authorizationEmail');template.url=authInfo.getAuthorizationUrl();template.notice=NOTICE;constmessage=template.evaluate();MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'AuthorizationRequired',message.getContent(),{name:ADDON_TITLE,htmlBody:message.getContent()});}settings.setProperty('lastAuthEmailDate',today);}}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Sends out creator notification email(s) if the current number * of form responses is an even multiple of the response step * setting. */functionsendCreatorNotification(){try{constform=FormApp.getActiveForm();constsettings=PropertiesService.getDocumentProperties();letresponseStep=settings.getProperty('responseStep');responseStep=responseStep?parseInt(responseStep):10;// If the total number of form responses is an even multiple of the// response step setting, send a notification email(s) to the form// creator(s). For example, if the response step is 10, notifications// will be sent when there are 10, 20, 30, etc. total form responses// received.if(form.getResponses().length%responseStep===0){constaddresses=settings.getProperty('creatorEmail').split(',');if(MailApp.getRemainingDailyQuota() > addresses.length){consttemplate=HtmlService.createTemplateFromFile('creatorNotification');template.summary=form.getSummaryUrl();template.responses=form.getResponses().length;template.title=form.getTitle();template.responseStep=responseStep;template.formUrl=form.getEditUrl();template.notice=NOTICE;constmessage=template.evaluate();MailApp.sendEmail(settings.getProperty('creatorEmail'),form.getTitle()+':Formsubmissionsdetected',message.getContent(),{name:ADDON_TITLE,htmlBody:message.getContent()});}}}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}/** * Sends out respondent notification emails. * * @param {FormResponse} response FormResponse object of the event * that triggered this notification */functionsendRespondentNotification(response){try{constform=FormApp.getActiveForm();constsettings=PropertiesService.getDocumentProperties();constemailId=settings.getProperty('respondentEmailItemId');constemailItem=form.getItemById(parseInt(emailId));constrespondentEmail=response.getResponseForItem(emailItem).getResponse();if(respondentEmail){consttemplate=HtmlService.createTemplateFromFile('respondentNotification');template.paragraphs=settings.getProperty('responseText').split('\n');template.notice=NOTICE;constmessage=template.evaluate();MailApp.sendEmail(respondentEmail,settings.getProperty('responseSubject'),message.getContent(),{name:form.getTitle(),htmlBody:message.getContent()});}}catch(e){// TODO (Developer) - Handle exceptionconsole.log('Failedwitherror:%s',e.error);}}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<style>
.branding-below {
bottom: 54px;
top: 0;
}
.branding-text {
left: 7px;
position: relative;
top: 3px;
}
.logo {
vertical-align: middle;
}
.width-100 {
width: 100%;
box-sizing: border-box;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
}
label {
font-weight: bold;
}
#creator-options,
#respondent-options {
background-color: #eee;
border-color: #eee;
border-width: 5px;
border-style: solid;
display: none;
}
#creator-email,
#respondent-email,
#button-bar,
#submit-subject {
margin-bottom: 10px;
}
#response-step {
display: inline;
}
</style>
</head>
<body>
<div class="sidebar branding-below">
<form>
<div class="block">
<input type="checkbox" id="creator-notify">
<label for="creator-notify">Notify me</label>
</div>
<div class="block form-group" id="creator-options">
<label for="creator-email">
My email addresses (comma-separated)
</label>
<input type="text" class="width-100" id="creator-email">
<label for="response-step">Send notifications after every</label>
<input type="number" id="response-step" value="10"
min="1" max="99999"> responses (default 10)
</div>
<div class="block">
<input type="checkbox" id="respondent-notify">
<label for="respondent-notify">Notify respondents</label>
</div>
<div class="block form-group" id="respondent-options">
<label for="respondent-email">
Which question asks for their email?
</label>
<select class="width-100" id="respondent-email"></select>
<label for="submit-subject">
Notification email subject:
</label>
<input type="text" class="width-100" id="submit-subject">
<label for="submit-notice">Notification email body:</label>
<textarea rows="8" cols="40" id="submit-notice"
class="width-100"></textarea>
</div>
<div class="block" id="button-bar">
<button class="action" id="save-settings">Save</button>
</div>
</form>
</div>
<div class="sidebar bottom">
<img alt="Add-on logo" class="logo" width="25"
src="https://g-suite-documentation-images.firebaseapp.com/images/newFormNotificationsicon.png">
<span class="gray branding-text">Form Notifications by Google</span>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
/**
* On document load, assign required handlers to each element,
* and attempt to load any saved settings.
*/
$(function() {
$('#save-settings').click(saveSettingsToServer);
$('#creator-notify').click(toggleCreatorNotify);
$('#respondent-notify').click(toggleRespondentNotify);
$('#response-step').change(validateNumber);
google.script.run
.withSuccessHandler(loadSettings)
.withFailureHandler(showStatus)
.withUserObject($('#button-bar').get())
.getSettings();
});
/**
* Callback function that populates the notification options using
* previously saved values.
*
* @param {Object} settings The saved settings from the client.
*/
function loadSettings(settings) {
$('#creator-email').val(settings.creatorEmail);
$('#response-step').val(!settings.responseStep ?
10 : settings.responseStep);
$('#submit-subject').val(!settings.responseSubject ?
'Thank you for filling out our form!' :
settings.responseSubject);
$('#submit-notice').val(!settings.responseText ?
'Thank you for responding to our form!' :
settings.responseText);
if (settings.creatorNotify === 'true') {
$('#creator-notify').prop('checked', true);
$('#creator-options').show();
}
if (settings.respondentNotify === 'true') {
$('#respondent-notify').prop('checked', true);
$('#respondent-options').show();
}
// Fill the respondent email select box with the
// titles given to the form's text Items. Also include
// the form Item IDs as values so that they can be
// easily recovered during the Save operation.
for (var i = 0; i < settings.textItems.length; i++) {
var option = $('<option>').attr('value', settings.textItems[i]['id'])
.text(settings.textItems[i]['title']);
$('#respondent-email').append(option);
}
$('#respondent-email').val(settings.respondentEmailItemId);
}
/**
* Toggles the visibility of the form creator notification options.
*/
function toggleCreatorNotify() {
$('#status').remove();
if ($('#creator-notify').is(':checked')) {
$('#creator-options').show();
} else {
$('#creator-options').hide();
}
}
/**
* Toggles the visibility of the form sumbitter notification options.
*/
function toggleRespondentNotify() {
$('#status').remove();
if($('#respondent-notify').is(':checked')) {
$('#respondent-options').show();
} else {
$('#respondent-options').hide();
}
}
/**
* Ensures that the entered step is a number between 1
* and 99999, inclusive.
*/
function validateNumber() {
var value = $('#response-step').val();
if (!value) {
$('#response-step').val(10);
} else if (value < 1) {
$('#response-step').val(1);
} else if (value > 99999) {
$('#response-step').val(99999);
}
}
/**
* Collects the options specified in the add-on sidebar and sends them to
* be saved as Properties on the server.
*/
function saveSettingsToServer() {
this.disabled = true;
$('#status').remove();
var creatorNotify = $('#creator-notify').is(':checked');
var respondentNotify = $('#respondent-notify').is(':checked');
var settings = {
'creatorNotify': creatorNotify,
'respondentNotify': respondentNotify
};
// Only save creator options if notify is turned on
if (creatorNotify) {
settings.responseStep = $('#response-step').val();
settings.creatorEmail = $('#creator-email').val().trim();
// Abort save if entered email is blank
if (!settings.creatorEmail) {
showStatus('Enter an owner email', $('#button-bar'));
this.disabled = false;
return;
}
}
// Only save respondent options if notify is turned on
if (respondentNotify) {
settings.respondentEmailItemId = $('#respondent-email').val();
settings.responseSubject = $('#submit-subject').val();
settings.responseText = $('#submit-notice').val();
}
// Save the settings on the server
google.script.run
.withSuccessHandler(
function(msg, element) {
showStatus('Saved settings', $('#button-bar'));
element.disabled = false;
})
.withFailureHandler(
function(msg, element) {
showStatus(msg, $('#button-bar'));
element.disabled = false;
})
.withUserObject(this)
.saveSettings(settings);
}
/**
* Inserts a div that contains an status message after a given element.
*
* @param {String} msg The status message to display.
* @param {Object} element The element after which to display the Status.
*/
function showStatus(msg, element) {
var div = $('<div>')
.attr('id', 'status')
.attr('class','error')
.text(msg);
$(element).after(div);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
</head>
<body>
<div>
<p>
<i>Form Notifications</i> was created as an sample add-on, and is meant
for demonstration purposes only. It should not be used for complex or
important workflows.
</p>
<p>
The number of notifications this add-on produces are limited by the owner's
available email quota; it will not send email notifications if the owner's
daily email quota has been exceeded. Collaborators using this add-on on the
same form will be able to adjust the notification settings, but will not be
able to disable the notification triggers set by other collaborators.
</p>
</div>
</body>
</html>
<p>The Google Forms add-on <i>Form Notifications</i> is set to run automatically
whenever a form is submitted. The add-on was recently updated and it needs you
to re-authorize it to run on your behalf.</p>
<p>The add-on's automatic functions are temporarily disabled until you
re-authorize the add-on. You can accomplish this by opening one of the forms
using the add-on and running the add-on through the menu. Alternatively, you can
click this link to approve authorization directly:</p>
<p><a href="<?= url ?>">Click here</a> to re-authorize the add-on.</p>
<p>This notification email will be sent to you at most once per day until the
add-on is re-authorized.</p>
<hr>
<p style="font-size:80%">This automatic message was sent to you via the <i>Form
Notifications</i> add-on for Google Forms.
<?= notice ?></p>
<p><i>Form Notifications</i> (a Google Forms add-on) has detected that the form
titled <a href="<?= formUrl?>"><b><?= title ?></b></a> has received
<?= responses ?> responses so far.</p>
<p><a href="<?= summary ?>">Summary of form responses</a></p>
<p>You are receiving this email because an editor of this form configured
<i>Form Notifications</i> to alert you every time this form receives
<b><?= responseStep ?></b> responses.</p>
<p>To change this setting, or to stop receiving these notifications, have the
form owner or editors open the form and adjust the <i>Form Notifications</i>
add-on configuration via the "Configure notifications" menu item.</p>
<hr>
<p style="font-size:80%">This automatic message was sent to you via the <i>Form
Notifications</i> add-on for Google Forms.
<?= notice ?></p>
<? for (var i = 0; i < paragraphs.length; i++) { ?>
<p><?= paragraphs[i] ?></p>
<? } ?>
<hr>
<p style="font-size:80%">This automatic message was sent to you via the <i>Form
Notifications</i> add-on for Google Forms.
<?= notice ?></p>
[[["わかりやすい","easyToUnderstand","thumb-up"],["問題の解決に役立った","solvedMyProblem","thumb-up"],["その他","otherUp","thumb-up"]],[["必要な情報がない","missingTheInformationINeed","thumb-down"],["複雑すぎる / 手順が多すぎる","tooComplicatedTooManySteps","thumb-down"],["最新ではない","outOfDate","thumb-down"],["翻訳に関する問題","translationIssue","thumb-down"],["サンプル / コードに問題がある","samplesCodeIssue","thumb-down"],["その他","otherDown","thumb-down"]],["最終更新日 2025-02-14 UTC。"],[[["This Google Forms add-on automates email notifications to form creators and/or respondents upon submission."],["Customizable settings allow control over notification frequency, recipient emails, and content."],["The add-on utilizes Google Apps Script triggers to execute functions when the form is opened, installed, or submitted."],["Users configure notification preferences through a sidebar accessible within the Google Form."],["The script manages authorization to access Google services for sending emails and interacting with form data."]]],["This Google Forms add-on automates email notifications upon form submissions. It notifies the form creator when a set number of responses are received and sends a custom message to respondents. Users configure notification frequency, recipient emails, and custom messages via a sidebar. The add-on manages authorization, sends re-authorization requests, and respects email quotas. Key actions include saving settings, managing triggers, handling form submissions, sending notifications, and validating user inputs. The code structure comprises the script (`code.gs`) and the HTML code (`sidebar.html`, `about.html`, `authorizationEmail.html`, `creatorNotification.html`, `respondentNotification.html`).\n"]]