
先日、「ホームページの問い合わせで画像も送ってもらえるようにしたい」と言われ、どのようにすればいいかを検討してみた。
PHPが使えるサーバーを使っているのであれば、formで送信してサーバーで受け取ればいいのですが、
個人的には、やっぱりお手軽なGoogleFormを使って行いたいという欲求がある。
さらに、小規模サイトの場合は、わざわざサーバーセットなどしなくても、Googleの無料枠でスプシ管理からデータ(画像)管理までできてしまったら、非常に便利だと思いません?
そして、いろいろ調べて、紆余曲折ありながら、小規模webサイトで使える画像も送信できるGoogleFormの拡張方法を見出せたので、
ブログに書き残しておきたいと思います。
GoogleFormの画像アップロード機能
一昔前は、文字列しか送信できなかった、Google Formも、今では、ファイルアップロード機能がついています。
【注意事項】でも、この設定を使っては行けません。
なぜなら、この機能をセットしてしまうと、WebサイトにGoogleFormを埋め込めなくなってしまうからです。
GoogleFormの発行URLでアクセスしたフォームまたは、iframeを使って埋め込む方法でよければ、これを使えば問題ありません。
しかし、ホームページのデザインやUIなどを重視したい場合は、GoogleFormのデフォルトデザインをそのまま使うというのは、かなり厳しいでしょう。
ちなみに、デフォルトのGoogleFormでファイルアップロードをしたら、GoogleDriveにファイルが保存される仕様で、スプレッドシートには、GoogleDriveのURLが追加される仕様です。
ということで、GoogleFormだけでのファイルアップロード機能はできないという結論に至りました。
GoogleDrive + GAS
そこで、Googleには、超絶便利なGoogleAppsScriptがあります。
これを使って、GoogleDriveにファイル(画像など)をアップロードできるようにすれば、問題なくね?という思考に行きつきました。
そして、GoogleFormには、GoogleDriveのURLを登録すれば、GoogleFormのアップロードと同じ結果にすることができます。
と言うことで、その手順の覚書です。
GoogleAppsScriptで新規プロジェクトを作る。
環境構築をするGoogleアカウントでログインした状態で、次のURLにアクセスすると、GAS (GoogleAppsScript)の新規プロジェクト作成になります。
https://script.google.com/home/projects/create
GASプログラム
表示されているエディタに以下のソースを貼り付けます。
const FOLDER_ID = " %GoogleDriveにフォルダを作ってURLのID部分を貼り付ける ";
function doPost(e) {
try {
const folder = DriveApp.getFolderById(FOLDER_ID);
const data = JSON.parse(e.postData.contents);
// data:image/jpeg;base64,...
const matches = data.image.match(/^data:(.+);base64,(.+)$/);
if (!matches) {
throw new Error("画像形式が不正です");
}
const mimeType = matches[1];
const bytes = Utilities.base64Decode(matches[2]);
const ext = mimeType.split("/")[1];
const filename =
Utilities.formatDate(
new Date(),
Session.getScriptTimeZone(),
"yyyyMMdd_HHmmss"
) + "." + ext;
const blob = Utilities.newBlob(bytes, mimeType, filename);
const file = folder.createFile(blob);
// リンクを知っている全員
file.setSharing(
DriveApp.Access.ANYONE_WITH_LINK,
DriveApp.Permission.VIEW
);
const fileId = file.getId();
const imageUrl =
"https://drive.google.com/uc?export=view&id=" + fileId;
return ContentService
.createTextOutput(JSON.stringify({
success: true,
fileId: fileId,
url: imageUrl
}))
.setMimeType(ContentService.MimeType.JSON);
} catch(err){
return ContentService
.createTextOutput(JSON.stringify({
success:false,
message:err.toString()
}))
.setMimeType(ContentService.MimeType.JSON);
}
}
デプロイセット
1. 「デプロイ」→「新しいデプロイ」
2. 種類 : ウェブアプリ
3. 実行者 : 自分
4. アクセスできるユーザー : 全員
上記をセットした後、ウェブアプリのURLを取得。
※こんなヤーツ
https://script.google.com/macros/s/XXXXXXXX/exec
これをこの後セットするホームページ側のJavascriptにコピペします。
ホームページの設定
実際に、サンプル構築したソースコードを掲載しておく。
※ハッシュやIDは、伏せています。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link href="css/style.css" rel="stylesheet" type="text/css">
<meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0" name="viewport">
<link href="favicon.ico" rel="icon" type="image/x-icon">
<link href="favicon.ico" rel="apple-touch-icon" type="image/x-icon">
<link href="https://example.com/" rel="canonical">
<title>タイトル</title>
<meta content="ホームページ説明" name="description">
<script type="module" src="js/main.js"></script>
<!-- OGP -->
<meta property="og:title" content="ページタイトル" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://example.com/" />
<meta property="og:image" content="https://example.com/" />
<meta property="og:site_name" content="ホームページタイトル" />
<meta property="og:description" content="ホームページ説明" />
<meta property="og:locale" content="ja_JP" />
<meta property="fb:app_id" content="" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content="" />
<meta name="twitter:site" content="@アカウント名" />
<meta name="twitter:image" content="https://example.com" />
<meta property="image_src" content="https://example.com" />
</head>
<body>
<form id="form_1" method='post'>
<input type="hidden" name="entry.xxxxx" data-name="FILE_ID">
<input type="hidden" name="entry.xxxxx" data-name="FILE_URL">
<p>何に関するお問い合わせですか?</p>
<input type="text" name="entry.xxxxx" required>
<p>名前</p>
<input type="text" name="entry.xxxxx" required>
<p>メールアドレス</p>
<input type="text" name="entry.xxxxx" required>
<p>お問い合わせ内容</p>
<input type="text" name="entry.xxxxx" required>
<p>写真</p>
<label for="photo">写真を選択</label>
<button type="submit" name="submit">送信</button>
</form>
<input id="photo" type="file" accept="image/jpeg,image/png,image/webp" required/>
<iframe
id="complete"
name="complete"
style="display:none;"></iframe>
</body>
</html>
css/style.css
form{
display:flex;
flex-direction:column;
gap:0.5em;
}
form input[type="text"]{
border:1px solid black;
border-radius:0.3em;
padding:0.3em 0.5em;
width:300px;
}
form button[name="submit"]{
width:300px;
border-radius:0.3em;
border:1px solid blue;
background-color:blue;
color:white;
padding:1.0em;
cursor:pointer;
margin-top:2.0em;
}
form button[name="submit"]:hover{
opacity:0.7;
}
form p{
margin-bottom:0.1em;
}
form label[for="photo"]{
width:100px;
border:1px solid black;
border-radius:0.5em;
padding:0.5em;
text-align:center;
cursor:pointer;
}
form label[for="photo"]:hover{
background-color:#FEE;
}
#photo{
display:none;
}
js/main.css
import { GoogleForm } from "./google_form.js"
import { GoogleDrive } from "./google_drive.js"
class Main{
constructor(){
this.init()
}
init(){
new GoogleForm().init({
form_selector : "#form_1",
action : 'https://docs.google.com/forms/d/xxxxxx/formResponse',
iframe_selector : '#complete',
redirect : 'thanks.html',
})
new GoogleDrive().init({
input_type_selector : "#photo",
value_selector : "label[for='photo']",
input_file_id_selector : "input[data-name='FILE_ID']",
input_file_url_selector : "input[data-name='FILE_URL']",
})
}
}
switch(document.readyState) {
case "complete":
case "interactive":
new Main();break
default:
window.addEventListener("DOMContentLoaded", () => {new Main()})
}
js/google_form.js
export class GoogleForm {
init(option){
this.option = this.validate(option)
if(!this.option){return}
option.form.target = option.iframe.id
option.form.action = option.action
option.form.addEventListener("submit", this.submit.bind(this))
if(option.file_upload){
this.file_upload_init()
}
}
validate(option){
if(!option.form_selector){return}
option.form = document.querySelector(option.form_selector) || null
if(!option.form){return}
if(!option.action){return}
option.iframe = document.querySelector(option.iframe_selector) || null
if(!option.iframe){return}
if(option.file_upload_selector){
option.file_upload = document.querySelector(option.file_upload_selector) || null
}
return option
}
submit(e){
// submit処理
this.option.submit && this.option.submit()
// redirect
if(this.option.redirect){
location.href = this.option.redirect
}
}
}
js/google_drive.js
export class GoogleDrive {
GAS_URL = "https://script.google.com/macros/s/xxxxxxxx/exec"
init(options){
this.options = this.validate(options)
if(!this.options){return}
this.options.input_type.addEventListener("change", this.upload.bind(this))
}
validate(options){
if(!options || !options.input_type_selector){return}
options.input_type = document.querySelector(options.input_type_selector) || null
if(!options.input_type){return}
if(options.input_file_id_selector){
options.input_file_id = document.querySelector(options.input_file_id_selector) || null
}
if(options.input_file_url_selector){
options.input_file_url = document.querySelector(options.input_file_url_selector) || null
}
if(options.value_selector){
options.value_elm = document.querySelector(options.value_selector) || null
}
return options
}
upload(e){
e.preventDefault();
const file = document.querySelector("#photo").files[0];
if(!file){
alert("画像を選択してください");
return;
}
const reader = new FileReader();
reader.onload = async ()=>{
this.loading_on()
try{
const response = await fetch(this.GAS_URL,{
method:"POST",
body:JSON.stringify({
image:reader.result
})
});
const json = await response.json();
console.log(json);
if(!json.success){
alert(json.message);
return;
}
// ファイル名表示
if(this.options.value_elm){
this.options.value_elm.innerText = file.name
}
// ここでGoogleFormへPOST(取得したアップ後の画像情報の登録)
this.set_input_value(json)
this.loading_off()
}
catch(err){
this.loading_off()
console.error(err);
}
};
reader.readAsDataURL(file);
}
set_input_value(data){
if(this.options.input_file_id){
this.options.input_file_id.value = data.fileId
}
if(this.options.input_file_url){
this.options.input_file_url.value = data.url
}
}
loading_on(){
const div = document.createElement("div")
div.id = "loading"
div.style.position = "fixed"
div.style.top = 0
div.style.left = 0
div.style.width = "100%"
div.style.height = "100dvh"
div.style.display = "flex"
div.style.alignItems = "center"
div.style.justifyContent = "center"
div.style.backgroundColor = "rgba(0,0,0,0.5)"
div.style.zIndex = 9999
div.innerHTML = "<p style='color:white;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);'>アップロード中...</p>"
document.body.appendChild(div)
}
loading_off(){
const loading = document.getElementById("loading")
if(!loading){return}
loading.parentNode.removeChild(loading)
}
}
GoogleSpreadSheet
上記のコードで送信をしてみたところ、正常に画像のURLがちゃんとくっついてきていた。
※GoogleFormに、FILE_IDとFILE_URLを追加するのがミソ。(FILE_IDは、念の為の保険)
これに一手間加えて、
画像プレビュー用の列を追加して、以下をセットするだけで、自動的に画像表示までされるようになります。
=IMAGE(セル座標)
あとがき
今回は画像でやりましたが、GoogleDriveにアップできるファイルであれば、なんでも自分のホームページから送信できるようになります。
動画などの容量が大きいファイルは容量オーバーで送信できませんが、簡単なデータのやり取りや、今回のように画像をファイルアップしたり、お問い合わせの添付画像(資料)などが必要なフォームで同じように利用することができるようになります。
これまで、文字ベースだけのお問い合わせで、GoogleFormを使っていたんですが、ファイルアップロードによって、より幅のある使い方ができることがわかりました。
これを使った、仕事でのホームページ制作も問題ナシですね。
参考記事
これは便利、CGIを一切使わずにformデータが収集できる、Google form活用方法
0 件のコメント:
コメントを投稿