후원신청서는 다양한 정보들을 입력받아야 합니다. 따라서 구글 설문지(Form) 만으로는 충분하지 않습니다. 만일 html 문법을 알고 있다면, 구글 설문지와 같은 내용을 직접 만들어 낼 수 있습니다.
그리고 이를 통해 구글 스프레드 시트로 데이터를 전송해 저장하는 것 또한 가능해집니다.
이를 위한 예시로 후원신청서를 만들어보았습니다.
일반 텍스트(성명)부터, 숫자(후원금액), radio(성별), dropdown menu(후원분야), checkbox(영수증 발급 여부), 이미지(서명) 등 다양한 데이터를 스프레드 시트로 전송합니다.
구글 스프레드 시트와의 연동을 위해 Apps Script로 구현하였으며, 이를 위한 프론트엔드는 index.html, 백엔드는 Code.gs로 만들었습니다.
Code.gs 파일에서 변수(var)로 정의된 부분은 사용자의 상황에 맞게 수정해주셔야 합니다.
index.html은 기관 사정에 맞게 수정하시되, Code.gs와 연동해서 변수, id, name 등을 일치시켜주셔야 합니다.
서명 이미지를 그려서 저장하는 것은 외부 js를 링크로 참조토록 하였습니다.
https://cdn.jsdelivr.net/npm/signature_pad@5.0.3/dist/signature_pad.umd.min.js
Code.gs
// 스프레드시트 ID와 시트 이름, 서명이미지 저장 폴더 ID를 변수로 선언
var SPREADSHEET_ID = '128Gz-t32NuAWKF88LmOy2C1Irq-ozbZgDBnUx3yKKg'; // 데이터를 저장할 스프레드시트 ID
var SHEET_NAME = '시트1'; // 데이터를 저장할 스프레드시트의 시트명
var FOLDER_ID = '1DK2586sqTok-9jqcIng7936ZfxwcfFh'; // 서명 이미지가 저장될 폴더 ID
// 기존 명부가 있는 경우 불러오기 위한 용도로 굳이 필요없음
var DATA_SPREADSHEET_ID = '1KAcx389rnPclnwFLsgDMxTTmxdMB49QaJt-XHYod_8'; // 데이터를 가져올 스프레드시트 ID, 예) 회원명부
var DATA_SHEET_NAME = '명부'; // 기존 정보를 가져올 스프레드시트의 시트명
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
function getUserDataByName(name) {
var sheet = SpreadsheetApp.openById(DATA_SPREADSHEET_ID).getSheetByName(DATA_SHEET_NAME);
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
if (data[i][0] === name) { // 첫 번째 열에 이름이 있다고 가정
return {
name: data[i][0],
gender: data[i][1],
ssn_a: data[i][2],
address: data[i][3],
cphone_n: data[i][4],
phone_n: data[i][5]
};
}
}
return null;
}
function sendUserData(name, gender, ssn_a, ssn_b, cphone_n, phone_n, address, e_mail, s_term, s_type, s_money, s_how, s_item, s_item_q, s_unit, s_sector, s_why, s_path, receipt1, receipt2, a_date, signature) {
var sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
var imageUrl = saveSignatureToDrive(signature, name);
sheet.appendRow([
name, gender, ssn_a, ssn_b,
"'" + cphone_n, // 앞에 작은 따옴표 추가
"'" + phone_n, // 앞에 작은 따옴표 추가
address, e_mail, s_term, s_type, s_money, s_how,
s_item, s_item_q, s_unit, s_sector, s_why, s_path,
receipt1, receipt2, a_date, '=IMAGE("' + imageUrl + '")'
]);
}
function saveSignatureToDrive(signature, name) {
var folder = DriveApp.getFolderById(FOLDER_ID);
var blob = Utilities.newBlob(Utilities.base64Decode(signature.split(',')[1]), 'image/png', name + '_signature.png');
var file = folder.createFile(blob);
var fileId = file.getId();
return 'https://drive.google.com/uc?export=view&id=' + fileId;
}
index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<title>후원 신청서</title>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@5.0.3/dist/signature_pad.umd.min.js"></script>
<style>
#sig {
background-color: #e0e0e0;
border: 1px solid black;
width: 500px;
height: 300px;
}
#s_item_q, #ssn_a {
width: 6em;
}
#ssn_b {
width: 7em;
}
#loadingMessage, #savingMessage {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid black;
padding: 20px;
z-index: 1000;
}
</style>
</head>
<body>
<h1>○○복지관 후원신청서</h1>
<hr>
<form id="donationForm">
<label>이름</label>
<input type="text" id="name" name="name" placeholder="이름" required>
<button type="button" onclick="autofillForm()">찾기</button></br><br>
<label>성별</label>
<input type="radio" name="gender" value="남">남자
<input type="radio" name="gender" value="여">여자<br>
<label>주민등록번호</label>
<input type="text" id="ssn_a" name="ssn_a" size="6", maxlength="6"> -
<input type="password" id="ssn_b" name="ssn_b" size="7", maxlength="7" required><br>
<label>휴대전화</label>
<input type="text" id="cphone_n" name="cphone_n" size="11" maxlength="11"> ※ 숫자만 입력<br>
<label>일반전화</label>
<input type="text" id="phone_n" name="phone_n" size="11" maxlength="11"> ※ 숫자만 입력<br>
<label>주소</label>
<input type="text" id="address" name="address" size="30"><br>
<label>이메일</label>
<input type="text" id="e_mail" name="e_mail" size="30"><br><br>
<label>후원방식</label>
<input type="radio" name="s_term" value="정기">정기
<input type="radio" name="s_term" value="비정기">비정기<br>
<label>후원구분</label>
<input type="radio" name="s_type" value="지정">지정
<input type="radio" name="s_type" value="비지정">비지정<br><br>
<label>후원금</label>
<input type="text" id="s_money" name="s_money">원<br>
<label>후원방법</label>
<input type="radio" name="s_how" value="자동이체">자동이체
<input type="radio" name="s_how" value="현금납부">현금납부
<input type="radio" name="s_how" value="CMS">CMS 이체
<input type="radio" name="s_how" value="기타">기타<br><br>
<label>후원품</label><br>
(품명) <input type="text" id="s_item" name="s_item"> /
(수량) <input type="number" id="s_item_q" name="s_item_q"> /
(단위) <select id="s_unit" name="s_unit">
<option value="">---</option>
<option value="개">개</option>
<option value="세트">세트</option>
<option value="Box">Box</option>
<option value="건">건</option>
<option value="Kg">Kg</option>
<option value="명">명</option>
<option value="포">포</option>
<option value="g">g(그램)</option>
<option value="리터">리터</option>
<option value="대">대</option>
<option value="장">장</option>
<option value="통">통</option>
<option value="판">판</option>
<option value="병">병</option>
<option value="마리">마리</option>
<option value="봉지">봉지</option>
<option value="팩">팩</option>
<option value="묶음">묶음</option>
<option value="그루">그루</option>
<option value="송이">송이</option>
<option value="포기">포기</option>
<option value="자루">자루</option>
<option value="인분">인분</option>
<option value="짝">짝</option>
<option value="포대">포대</option>
<option value="기타">기타</option>
</select><br><br>
<label>후원분야</label>
<select id="s_sector" name="s_sector">
<option value="">-------------------------------</option>
<option value="밑반찬지원">밑반찬지원</option>
<option value="저소득노인 무료급식사업">저소득노인 무료급식사업</option>
<option value="위기 및 취약노인 사업">위기 및 취약노인 사업</option>
<option value="지역복지 연계사업">지역복지 연계사업</option>
<option value="명절 정나눔 행사">명절 정나눔 행사</option>
<option value="기타">기타</option>
</select><br>
<label>참여동기</label>
<select id="s_why" name="s_why">
<option value="">-------------------------------</option>
<option value="경제적 여유가 있어서">경제적 여유가 있어서</option>
<option value="불우한 이웃을 돕기위해">불우한 이웃을 돕기위해</option>
<option value="지역사회복지를 위해">지역사회복지를 위해</option>
<option value="종교적 신념으로">종교적 신념으로</option>
<option value="기타">기타</option>
</select><br>
<label>참여경로</label>
<select id="s_path" name="s_path">
<option value="">-------------------------------</option>
<option value="기존 후원자/자원봉사자 소개를 통해">기존 후원자/자원봉사자 소개를 통해</option>
<option value="복지관 소식지나 안내물을 통해">복지관 소식지나 안내물을 통해</option>
<option value="언론매체를 통해">언론매체를 통해(TV, 라디오, 신문 등)</option>
<option value="특정 행사 및 기념일 축하">특정 행사 및 기념일 축하</option>
<option value="기타">기타</option>
</select><br><br>
<label>후원금영수증 발급</label><br>
<input type="checkbox" name="receipt1" value="후원금영수증 필요">후원금 영수증 필요<br>
<input type="checkbox" name="receipt2" value="국세청 연말정산 간소화 후원내역 제공 동의">국세청 연말정산 간소화 후원내역 제공 동의 <br><br>
<label>신청일자</label>
<input type="date" id="a_date" name="a_date"><br><br>
<label>서명</label><br>
<canvas id="sig"></canvas><br>
<button type="button" id="clearSig">서명 지우기</button><br><br>
<button type="button" onclick="submitData()">제출</button>
※ 제출완료에 다소 시간이 걸립니다. 잠시만 기다려주세요.
</form>
<div id="loadingMessage">검색 중입니다...</div>
<div id="savingMessage">저장 중입니다...</div>
<script>
var signaturePad;
function setupSignatureBox() {
var canvas = document.getElementById("sig");
canvas.width = 500; // 명시적으로 크기 설정
canvas.height = 300; // 명시적으로 크기 설정
signaturePad = new SignaturePad(canvas);
}
function clearSignature() {
signaturePad.clear();
}
function submitData() {
const form = document.getElementById('donationForm');
const formData = new FormData(form);
// 필수 입력 필드 확인
if (!formData.get('name') || !formData.get('ssn_a') || !formData.get('ssn_b') || !formData.get('cphone_n') || signaturePad.isEmpty()) {
alert('성명, 주민등록번호, 연락처, 서명은 필수 입력 항목입니다.');
return;
}
const data = {
name: formData.get('name'),
gender: formData.get('gender'),
ssn_a: formData.get('ssn_a'),
ssn_b: formData.get('ssn_b'),
cphone_n: formData.get('cphone_n'),
phone_n: formData.get('phone_n'),
address: formData.get('address'),
e_mail: formData.get('e_mail'),
s_term: formData.get('s_term'),
s_type: formData.get('s_type'),
s_money: formData.get('s_money'),
s_how: formData.get('s_how'),
s_item: formData.get('s_item'),
s_item_q: formData.get('s_item_q'),
s_unit: formData.get('s_unit'),
s_sector: formData.get('s_sector'),
s_why: formData.get('s_why'),
s_path: formData.get('s_path'),
receipt1: formData.get('receipt1') ? '필요' : '불필요',
receipt2: formData.get('receipt2') ? '동의' : '미동의',
a_date: formData.get('a_date'),
signature: signaturePad.toDataURL()
};
document.getElementById('savingMessage').style.display = 'block'; // 저장 중 메시지 표시
google.script.run.withSuccessHandler(() => {
document.getElementById('savingMessage').style.display = 'none'; // 저장 중 메시지 숨기기
alert('데이터가 성공적으로 저장되었습니다.');
}).withFailureHandler(error => {
document.getElementById('savingMessage').style.display = 'none'; // 저장 중 메시지 숨기기
console.error('Error:', error);
alert('데이터 저장 중 오류가 발생했습니다. 다시 입력해주세요.');
}).sendUserData(
data.name, data.gender, data.ssn_a, data.ssn_b, data.cphone_n, data.phone_n,
data.address, data.e_mail, data.s_term, data.s_type, data.s_money, data.s_how,
data.s_item, data.s_item_q, data.s_unit, data.s_sector, data.s_why, data.s_path,
data.receipt1, data.receipt2, data.a_date, data.signature
);
}
function autofillForm() {
const name = document.getElementById('name').value;
document.getElementById('loadingMessage').style.display = 'block'; // 로딩 메시지 표시
google.script.run.withSuccessHandler(fillForm).getUserDataByName(name);
}
function fillForm(data) {
document.getElementById('loadingMessage').style.display = 'none'; // 로딩 메시지 숨기기
if (data) {
document.getElementById('ssn_a').value = data.ssn_a;
document.getElementById('address').value = data.address;
document.getElementById('cphone_n').value = data.cphone_n;
document.getElementById('phone_n').value = data.phone_n;
// 성별 필드 업데이트
if (data.gender === '남') {
document.querySelector('input[name="gender"][value="남"]').checked = true;
} else if (data.gender === '여') {
document.querySelector('input[name="gender"][value="여"]').checked = true;
}
} else {
alert('해당 이름의 데이터를 찾을 수 없습니다.');
}
}
document.getElementById("clearSig").addEventListener("click", clearSignature);
document.addEventListener("DOMContentLoaded", setupSignatureBox);
</script>
</body>
</html>