[英]Google Apps Scripts: Function running twice from .showModalDialog form submit
這是我的第一篇文章,我已經搜索了幾天,但找不到解決此問題的方法。
我在工作表中有一個自定義菜單,可以彈出 .showModalDialog html。 用戶填寫信息並點擊提交。 這會在后端運行一個創建文件夾/文件並將用戶數據添加到各種工作表等的功能。
所有這些都在工作 我有一個 Ui.alert,我用它來檢查輸入的數據是否正確,由於某種原因,該功能被觸發兩次,結果 UI.alert 再次彈出。 我有一個故障保險櫃,可以檢查其中一個字段是否存在,因此它不會再次寫入,但彈出窗口是一種非常糟糕的用戶體驗。
任何幫助將非常感激。 創建自定義菜單的功能:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('TOA - Menu')
.addItem('Add New Account', 'addAccount')
.addItem('Update Brand', 'updateBrand')
.addItem('Go Live', 'goLive')
.addToUi();
}
調出表格的功能:
function addAccount() {
const html = HtmlService.createHtmlOutputFromFile('newAccount')
.setTitle('Add New Account')
.setWidth(1000)
.setHeight(800);;
SpreadsheetApp.getUi()
.showModalDialog(html, 'Add New Account');
}
表格代碼:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
#itemDisplay {
display: none;
}
#modDisplay {
display: none;
}
#priceDisplay {
display: none;
}
#businessTypeDisplay {
display: none;
}
</style>
</head>
<body>
<h1>
Add New Account
</h1>
<form id="myForm" onsubmit="handleNewAccountFormSubmit(this);">
<div>
<label for="newBrand">Brand:</label>
<input name="newBrand" type="text" placeholder="Brand Name" required>
</div>
<div>
<p>Location</p>
<label for="country">Country:</label>
<select name="country" id="" onchange="" required>
<option value="" disabled selected>Select Country</option>
<option value="US">US</option>
</select>
<label for="state">State/Province:</label>
<input name="state" type="text" placeholder="State or province" required>
<label for="city">City:</label>
<input name="city" type="text" placeholder="City" required>
<div>
<label for="businessType">Business Type:</label>
<select name="businessType" id="businessTypeSelect" onchange="businessOtherDisplay(this.value);" required>
<option value="" disabled selected>Select Request Reason</option>
<option value="americanDiner">American Diner</option>
<option value="pizzaParlor">Pizza Parlor</option>
<option value="coffeeShop">Coffee Shop</option>
<option value="candyShop">Candy Store</option>
<option value="iceCreamParlor">Ice Cream Parlor</option>
<option value="burgerShop">Burger Shop</option>
<option value="otherNon_AmericanDiner">Other non-American Diner (Foreign servings)</option>
<option value="other">Other (not listed above)</option>
</select>
</div>
<div id="businessTypeDisplay">
<label for="businessTypeOther">Business Type Other:</label>
<input name="businessTypeOther" type="text" placeholder="Business type if not listed above">
</div>
<div>
<label for="integration">Integration:</label>
<select name="integration" required>
<option value="" disabled selected>Select Request Reason</option>
<option value="square">Square</option>
<option value="clover">Clover</option>
<option value="cloverPilot">Clover Pilot</option>
<option value="stripe">Stripe</option>
<option value="gPay">GPAY</option>
<option value="others" >Others</option>
</select>
</div>
<label for="menuSource">File Attachment/Source:</label>
<input name="menuSource" type="text" placeholder="Path to menu" required url>
<div>
<p>Do you need an item hidden/disabled?</p>
<label for="yes">Yes</label>
<input name="disableItemOption" type="radio" value="yes" onclick="showItem()">
<label for="no">No</label>
<input name="disableItemOption" type="radio" value="no" checked onclick="hideItem()">
</div>
<div id="itemDisplay">
<label for="itemDisable">Which item(s) should be disabled?</label>
<textarea id="disabledItem" name="itemDisable" cols="40" rows="5"></textarea>
</div>
<div>
<p>Do you need a modifier hidden/disabled?</p>
<label for="yes">Yes</label>
<input name="disableModOption" type="radio" value="yes" onclick="showMod()">
<label for="no">No</label>
<input name="disableModOption" type="radio" value="no" checked onclick="hideMod()">
</div>
<div id="modDisplay">
<label for="modDisable">Which modifier(s) should be disbaled?</label>
<textarea id="disabledMod" name="modDisable" cols="40" rows="5"></textarea>
</div>
<div>
<p>Do you need to update a price?</p>
<label for="yes">Yes</label>
<input name="updatePrice" type="radio" value="yes" onclick="showPrice()">
<label for="no">No</label>
<input name="updatePrice" type="radio" value="no" checked onclick="hidePrice()">
</div>
<div id="priceDisplay">
<label for="priceUpdate">Which item/modifier needs a price update?</label>
<textarea id="updatedPrice" name="priceUpdate" cols="40" rows="5" priceUpdate></textarea>
</div>
<div>
<label for="otherUpdates">Any other information needed on the menu?</label>
<input name="otherUpdates" type="text" placeholder="List other instructions here">
</div>
<div>
<label for="specialInstructions">Are there special instructions/notes for this brand?</label>
<input name="specialInstructions" type="text" placeholder="List special instructions here">
</div>
<input id="submitButton" type="submit" value="Submit">
<input type="button" value="Cancel" onclick="google.script.host.close()">
</div>
</form>
<script>
function handleNewAccountFormSubmit(formObject) {
document.getElementById('submitButton').disabled=true;
google.script.run.withSuccessHandler().processNewAccountForm(formObject);
}
function disableSubmit() {
document.getElementById('submitButton').disabled=true;
document.getElementById('submitButton').value='Sending...';
}
function showItem(){
document.getElementById('itemDisplay').style.display ='block';
document.getElementById('disabledItem').required = true;
};
function hideItem(){
document.getElementById('itemDisplay').style.display = 'none';
document.getElementById('disabledItem').required = false;
};
function showMod(){
document.getElementById('modDisplay').style.display ='block';
document.getElementById('disabledMod').required = true;
};
function hideMod(){
document.getElementById('modDisplay').style.display = 'none';
document.getElementById('disabledMod').required = false;
};
function showPrice(){
document.getElementById('priceDisplay').style.display ='block';
document.getElementById('updatedPrice').required = true;
};
function hidePrice(){
document.getElementById('priceDisplay').style.display = 'none';
document.getElementById('updatedPrice').required = false;
};
function businessOtherDisplay(value) {
if(value === "other") {
document.getElementById('businessTypeDisplay').style.display = 'block';
} else {
document.getElementById('businessTypeDisplay').style.display = 'none';
};
};
</script>
</body>
</html>
以及處理邏輯的代碼
function processNewAccountForm(formObject) {
const ui = SpreadsheetApp.getUi();
const ass = SpreadsheetApp.getActiveSpreadsheet();
const ss = ass.getActiveSheet();
const timestamp = Utilities.formatDate(new Date(), "GMT+8", "MM/dd/yyyy HH:mm:ss");
const userEmail = Session.getActiveUser().getEmail();
const brandName = formObject.newBrand;
// Add alert to check data entered is correct
const response = ui.alert('Please confirm the following information is correct:',
'𝗕𝗿𝗮𝗻𝗱 𝗡𝗮𝗺𝗲: ' + formObject.newBrand +
'\n𝗖𝗼𝘂𝗻𝘁𝗿𝘆: ' + formObject.country +
'\n𝗦𝘁𝗮𝘁𝗲: ' + formObject.state +
'\n𝗖𝗶𝘁𝘆: ' + formObject.city +
'\n𝗕𝘂𝘀𝗶𝗻𝗲𝘀𝘀 𝗧𝘆𝗽𝗲: ' + formObject.businessType +
'\n𝗜𝗻𝘁𝗲𝗴𝗿𝗮𝘁𝗶𝗼𝗻: ' + formObject.integration +
'\n𝗙𝗶𝗹𝗲 𝗦𝗼𝘂𝗿𝗰𝗲: ' + formObject.menuSource +
'\n𝗗𝗼 𝘆𝗼𝘂 𝗻𝗲𝗲𝗱 𝗮𝗻 𝗶𝘁𝗲𝗺 𝗵𝗶𝗱𝗱𝗲𝗻/𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.disableItemOption +
'\n𝗪𝗵𝗶𝗰𝗵 𝗶𝘁𝗲𝗺(𝘀) 𝘀𝗵𝗼𝘂𝗹𝗱 𝗯𝗲 𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.itemDisable +
'\n𝗗𝗼 𝘆𝗼𝘂 𝗻𝗲𝗲𝗱 𝗮 𝗺𝗼𝗱𝗶𝗳𝗶𝗲𝗿 𝗵𝗶𝗱𝗱𝗲𝗻/𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.disableModOption +
'\n𝗪𝗵𝗶𝗰𝗵 𝗺𝗼𝗱𝗶𝗳𝗶𝗲𝗿𝘀 𝘀𝗵𝗼𝘂𝗹𝗱 𝗯𝗲 𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.modDisable +
'\n𝗗𝗼 𝘆𝗼𝘂 𝗻𝗲𝗲𝗱 𝘁𝗼 𝘂𝗽𝗱𝗮𝘁𝗲 𝗮 𝗽𝗿𝗶𝗰𝗲?: ' + formObject.updatePrice +
'\n𝗪𝗵𝗶𝗰𝗵 𝗶𝘁𝗲𝗺/𝗺𝗼𝗱𝗶𝗳𝗶𝗲𝗿 𝗻𝗲𝗲𝗱𝘀 𝗮 𝗽𝗿𝗶𝗰𝗲 𝘂𝗽𝗱𝗮𝘁𝗲?: ' + formObject.priceUpdate +
'\n𝗔𝗻𝘆 𝗼𝘁𝗵𝗲𝗿 𝗶𝗻𝗳𝗼𝗿𝗺𝗮𝘁𝗶𝗼𝗻 𝗻𝗲𝗲𝗱𝗲𝗱 𝗼𝗻 𝘁𝗵𝗲 𝗺𝗲𝗻𝘂?: ' + formObject.otherUpdates +
'\n𝗔𝗿𝗲 𝘁𝗵𝗲𝗿𝗲 𝘀𝗽𝗲𝗰𝗶𝗮𝗹 𝗶𝗻𝘀𝘁𝗿𝘂𝗰𝘁𝗶𝗼𝗻𝘀/𝗻𝗼𝘁𝗲𝘀 𝗳𝗼𝗿 𝘁𝗵𝗶𝘀 𝗯𝗿𝗮𝗻𝗱?: ' + formObject.specialInstructions
, ui.ButtonSet.YES_NO);
if(response === ui.Button.YES) {
var lock = LockService.getScriptLock();
lock.waitLock(60000);
try {
const brandColumn = ss.getRange('D:D');
const brandValues = brandColumn.getValues();
let i = 1;
// Check for exisiting brand name
for(i=1; i < brandValues.length; i++) {
if(brandValues[i].toString().toLowerCase().trim() == brandName.toString().toLowerCase().trim() && ss.getRange(i+1,5).getValue() == 'New Brand'){
ui.alert("Brand name already created");
return;
}
};
// Create folder and PDF with build instructions
const parentFolder = DriveApp.getFolderById("RemovedfolderID");// how does this work with Shared drives? Create and move?
// const parentFolder = DriveApp.getFolderById("RemovedfolderID"); < ---- Team drive ID (notworking..) My folder -> RemovedfolderID
const newFolder = parentFolder.createFolder(brandName);
const docFile = newFolder.createFile(brandName+'.pdf',
'𝗕𝗿𝗮𝗻𝗱 𝗡𝗮𝗺𝗲: ' + formObject.newBrand +
'\n𝗖𝗼𝘂𝗻𝘁𝗿𝘆: ' + formObject.country +
'\n𝗦𝘁𝗮𝘁𝗲: ' + formObject.state +
'\n𝗖𝗶𝘁𝘆: ' + formObject.city +
'\n𝗕𝘂𝘀𝗶𝗻𝗲𝘀𝘀 𝗧𝘆𝗽𝗲: ' + formObject.businessType +
'\n𝗜𝗻𝘁𝗲𝗴𝗿𝗮𝘁𝗶𝗼𝗻: ' + formObject.integration +
'\n𝗙𝗶𝗹𝗲 𝗦𝗼𝘂𝗿𝗰𝗲: ' + formObject.menuSource +
'\n𝗗𝗼 𝘆𝗼𝘂 𝗻𝗲𝗲𝗱 𝗮𝗻 𝗶𝘁𝗲𝗺 𝗵𝗶𝗱𝗱𝗲𝗻/𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.disableItemOption +
'\n𝗪𝗵𝗶𝗰𝗵 𝗶𝘁𝗲𝗺(𝘀) 𝘀𝗵𝗼𝘂𝗹𝗱 𝗯𝗲 𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.itemDisable +
'\n𝗗𝗼 𝘆𝗼𝘂 𝗻𝗲𝗲𝗱 𝗮 𝗺𝗼𝗱𝗶𝗳𝗶𝗲𝗿 𝗵𝗶𝗱𝗱𝗲𝗻/𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.disableModOption +
'\n𝗪𝗵𝗶𝗰𝗵 𝗺𝗼𝗱𝗶𝗳𝗶𝗲𝗿𝘀 𝘀𝗵𝗼𝘂𝗹𝗱 𝗯𝗲 𝗱𝗶𝘀𝗮𝗯𝗹𝗲𝗱?: ' + formObject.modDisable +
'\n𝗗𝗼 𝘆𝗼𝘂 𝗻𝗲𝗲𝗱 𝘁𝗼 𝘂𝗽𝗱𝗮𝘁𝗲 𝗮 𝗽𝗿𝗶𝗰𝗲?: ' + formObject.updatePrice +
'\n𝗪𝗵𝗶𝗰𝗵 𝗶𝘁𝗲𝗺/𝗺𝗼𝗱𝗶𝗳𝗶𝗲𝗿 𝗻𝗲𝗲𝗱𝘀 𝗮 𝗽𝗿𝗶𝗰𝗲 𝘂𝗽𝗱𝗮𝘁𝗲?: ' + formObject.priceUpdate +
'\n𝗔𝗻𝘆 𝗼𝘁𝗵𝗲𝗿 𝗶𝗻𝗳𝗼𝗿𝗺𝗮𝘁𝗶𝗼𝗻 𝗻𝗲𝗲𝗱𝗲𝗱 𝗼𝗻 𝘁𝗵𝗲 𝗺𝗲𝗻𝘂?: ' + formObject.otherUpdates +
'\n𝗔𝗿𝗲 𝘁𝗵𝗲𝗿𝗲 𝘀𝗽𝗲𝗰𝗶𝗮𝗹 𝗶𝗻𝘀𝘁𝗿𝘂𝗰𝘁𝗶𝗼𝗻𝘀/𝗻𝗼𝘁𝗲𝘀 𝗳𝗼𝗿 𝘁𝗵𝗶𝘀 𝗯𝗿𝗮𝗻𝗱?: ' + formObject.specialInstructions,
MimeType.PDF);
const fileURL = docFile.getUrl();
// add header row to spreadsheet
// Create Spreadsheet in Brand folder. Activity log.
const name = brandName + " Activity Log";
const id = newFolder.getId();
const resource = {
title: name,
mimeType: MimeType.GOOGLE_SHEETS,
parents: [{id: id}]
};
const fileJson = Drive.Files.insert(resource);
const fileId = fileJson.id;
const lastRow = ss.getLastRow();
const newEntry = [
lastRow,
timestamp,
timestamp,
formObject.newBrand,
'New Brand',
formObject.businessType,
formObject.integration,
'=HYPERLINK("'+formObject.menuSource+'")',
userEmail,
fileURL,
,
,
,
,
formObject.city,
formObject.state,
formObject.country,
fileId
];
const newSheet = SpreadsheetApp.openById(fileId);
const sheetRange = newSheet.getSheetByName("Sheet1").getRange(1,1,1,18);
const headers = [
['𝗜𝗻𝗱𝗲𝘅',
'𝗕𝗿𝗮𝗻𝗱 𝗼𝗿𝗶𝗴𝗶𝗻𝗮𝗹 𝗰𝗿𝗲𝗮𝘁𝗲 𝗱𝗮𝘁𝗲',
'𝗧𝗶𝗺𝗲 𝗜𝗻',
'𝗕𝗿𝗮𝗻𝗱 𝗡𝗮𝗺𝗲',
'𝗥𝗲𝗾𝘂𝗲𝘀𝘁 𝗿𝗲𝗮𝘀𝗼𝗻',
'𝗕𝘂𝘀𝗶𝗻𝗲𝘀𝘀 𝘁𝘆𝗽𝗲',
'𝗜𝗻𝘁𝗲𝗴𝗿𝗮𝘁𝗶𝗼𝗻',
'𝗠𝗲𝗻𝘂 𝗦𝗼𝘂𝗿𝗰𝗲',
'𝗖𝗿𝗲𝗮𝘁𝗲𝗱 𝗯𝘆:',
'𝗠𝗲𝗻𝘂 𝗜𝗻𝘀𝘁𝗿𝘂𝗰𝘁𝗶𝗼𝗻𝘀',
'𝗔𝘀𝘀𝗶𝗴𝗻𝗲𝗱 𝘁𝗼?',
'𝗧𝗶𝗺𝗲 𝗢𝘂𝘁',
'𝗖𝗼𝗺𝗽𝗹𝗲𝘅𝗶𝘁𝘆 𝗥𝗮𝘁𝗶𝗻𝗴',
'𝗚𝗼 𝗹𝗶𝘃𝗲 𝗱𝗮𝘁𝗲 𝗮𝗻𝗱 𝘁𝗶𝗺𝗲',
'𝗖𝗶𝘁𝘆',
'𝗦𝘁𝗮𝘁𝗲/𝗣𝗿𝗼𝘃𝗶𝗻𝗰𝗲',
'𝗖𝗼𝘂𝗻𝘁𝗿𝘆',
'𝗔𝗰𝘁𝗶𝘃𝗶𝘁𝘆 𝗟𝗼𝗴']
];
sheetRange.setValues(headers);
// Add data to last row in main tracker
ss.appendRow(newEntry);
// Copy data to spreadsheet brand
const activitySheet = newSheet.getSheetByName("Sheet1")
activitySheet.appendRow(newEntry);
// Flush changes before releasing lock
SpreadsheetApp.flush();
} catch(e) {
ui.alert("System is Busy, Please try again in a moment.");
return
} finally {
lock.releaseLock();
return
}
} else {
// action to take if info is incorrect? or No is clicked
};
};
我知道根據 Cooper 和其他人在這里和這里的帖子,多個觸發器是一個已知問題,但我似乎無法將它們用於我的解決方案。
提前感謝您的任何想法。
這些是我對對話框所做的更改:
<input id="btn1" type="button" value="Submit" onClick="handleNewAccountFormSubmit(this.parentNode);" />
<input type="button" value="Cancel" onclick="google.script.host.close()">
</div>
</form>
<script>
function handleNewAccountFormSubmit(formObject) {
document.getElementById('btn1').disabled=true;
google.script.run.processNewAccountForm(formObject);
}
我並不是說你的方式是錯誤的。 這就是我會嘗試這樣做的方式。 但實際上,這是一個相當大的對話,您只需要深入研究並弄清楚。 如果我這樣做並且遇到了很多問題,我可能會從一個更簡單的版本開始,讓它工作,然后慢慢添加更多功能。
這實際上是一個相當困難的時間,在兩個運行時之間的轉換中間編寫代碼。 我剛剛注意到我在 ES5 的某些領域丟失了我的內容輔助,但它們現在可以在 ES6 中工作,所以事情可能很難處理。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.