質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

CSS

CSSはXMLやHTMLで表現した色・レイアウト・フォントなどの要素を指示する仕様の1つです。

Q&A

解決済

1回答

2415閲覧

jsとgasによるdoPostとfetch間によるhtmlファイルの受け渡しについて

seito

総合スコア3

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

jQuery

jQueryは、JavaScriptライブラリのひとつです。 簡単な記述で、JavaScriptコードを実行できるように設計されています。 2006年1月に、ジョン・レシグが発表しました。 jQueryは独特の記述法を用いており、機能のほとんどは「$関数」や「jQueryオブジェクト」のメソッドとして定義されています。

HTML

HTMLとは、ウェブ上の文書を記述・作成するためのマークアップ言語のことです。文章の中に記述することで、文書の論理構造などを設定することができます。ハイパーリンクを設定できるハイパーテキストであり、画像・リスト・表などのデータファイルをリンクする情報に結びつけて情報を整理します。現在あるネットワーク上のほとんどのウェブページはHTMLで作成されています。

CSS

CSSはXMLやHTMLで表現した色・レイアウト・フォントなどの要素を指示する仕様の1つです。

0グッド

2クリップ

投稿2023/01/25 13:04

編集2023/01/29 13:10

前提

SpreadShhetのwebアプリを利用して、QRコードを読み込むPOSシステムのようなものを考えています。
ここでは、読み取り後の購入情報等をgas側に送信しSpreadSheetへ書き込みを行うと同時にhtml側に領収書ページを表示させたいと考えています。
doPost関数へ値を渡し、SpreadSheetへの書き込み。htmlServiceにてファイルを作成するところまではできていると思います。

ですが、html側のfetchにて返信されたデータを確認するとobjectは受け取れるのですが、html側でページの遷移を起こすことができず、どのように実装してよいか悩んでおります。

実現したいこと

ここに実現したいことを箇条書きで書いてください。

  • html側から商品情報等をgasのdopost等へ送り、SpreadSheetにlogを残したい。
  • gas側で作成してある"領収書"htmlファイルに値をセットし、jsへ送り返したい。
  • doPostで返信されたhtmlをjs側で受け取り、ページ遷移をしたい。

発生している問題・エラーメッセージ

doPostでhtmlService.createHtmlOutputFromFile("result");を返却して、jsで確認すると以下のオブジェクトが確認できるが、doGetのようにページが表示されることはない。

Response {type: 'opaque', url: '', redirected: false, status: 0, ok: false, …}

該当のソースコード

gas

1doPost(e){ 2 const json_data = JSON.parse(e.postData.getDataAsString()); 3 const result = HtmlService.createHtmlOutpustFromFile("result"); 4 result.data = "付随したいデータ"; 5 6 return result.evaluate(); 7} 8 9function get_url(){ 10 // gasのURL取得関数(jsで呼び出す) 11 return ScriptApp.getService().getUrl(); 12}

js(index.html)

1function send_data() { 2 google.script.run.withSuccessHandler(e => { 3 const URL = e; 4 const postparam = { 5 "method" : "POST", 6 "mode" : "no-cors", 7 "Content-Type" : "application/x-www-form-urlencoded", 8 "body" : JSON.stringify(item_dict) 9 }; 10 11 const res = fetch(URL, postparam).then(response => response).then(data => { 12 // 読み取るタイミングやfetchの使い方が曖昧なため、これで合っているかすら疑問 13 // オブジェクトは取得できているが処理の方法を理解できていない、、? 14 console.log(data); 15 }); 16 }).get_url(); 17}

試したこと

doGetでresult.htmlを返却する事は考えたのですが、購入商品情報等のjsonを渡す方法をどう繋げればいよのか、また余計な考えかもしれませんがスマートに処理ができるのでは、、と考えdoPostでの画面遷移の方法を模索しています。

質問の中で、至らない所などあるとは思いますが
どうぞご教授のほどよろしくお願いいたします。

補足情報(FW/ツールのバージョンなど)

html, gas, google chromeなどは最新だと思います

// 以下gas ----------------------------------------------------------------------- function doGet(e) { // 最初に商品読み取り用のページを表示させる。(ここは表示できる) return HtmlService.createHtmlOutputFromFile("index"); } function doPost(e) { // 各種データの取得 const json_data = JSON.parse(e.postData.getDataAsString()); const sheet_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // 管理表で商品の売上数を計上 const sheet_name1 = "管理表"; // logに現在時刻とindexから返却された商品購入情報のjsonを記録 const sheet_name2 = "log"; // 領収書情報に記載されている情報(会社名や電話番号等)を取得 const sheet_name3 = "領収証情報"; const sheet1 = SpreadsheetApp.openById(sheet_id).getSheetByName(sheet_name1); const sheet2 = SpreadsheetApp.openById(sheet_id).getSheetByName(sheet_name2); const sheet3 = SpreadsheetApp.openById(sheet_id).getSheetByName(sheet_name3); const r_last1 = sheet1.getLastRow() + 1; const r_last2 = sheet2.getLastRow() + 1; const range_all1 = sheet1.getRange(1, 1, r_last1, 9); const range_all2 = sheet2.getRange(1, 1, r_last2, 2); const data_all1 = range_all1.getValues(); const data_all2 = range_all2.getValues(); const data_id = data_all1.map(elm => elm[0]); // 購入商品の合計金額 let amount = 0; // 商品の税率情報より求める内税額 let tax = 0; // 商品情報json処理 Object.keys(json_data).forEach(key => { const keyword = key.split(",")[0]; const r_hit1 = data_id.indexOf(keyword); const num = data_all1[r_hit1][5]; range_all1.offset(r_hit1, 5, 1, 1).setValue(num + json_data[key]); amount += key.split(",")[4] * json_data[key]; tax += key.split(",")[5] * json_data[key]; }); // シートへ書き込み const r_hit2 = data_all2.map(elm => elm[0]).indexOf(""); const date = Utilities.formatDate(new Date(), 'JST', "yyyy/MM/dd (E) HH:mm:ss Z"); range_all2.offset(r_hit2,0,1,2).setValues([[date, json_data]]); const info = sheet3.getRange(2, 1, 1, 5).getValues().flat(); // htmlデータ作成 const result = HtmlService.createHtmlOutputFromFile("result"); result.date = info[0]; result.zip = info[1]; result.address = info[2]; result.corp = info[3]; result.phone = info[4]; result.amount = amount; result.tax = tax; result.without = amount - tax; // 返却するが、表示できない return result.evaluate; } function get_url(){ // js側でrun関数で呼び出すデプロイurl取得用の関数 return ScriptApp.getService().getUrl(); } // 以下index.html ----------------------------------------------------------------------- <!DOCTYPE html> <html> <head> <base target="_top"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"/> <title>QRコードリーダ</title> <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script> </head> <!-- html --> <body> <div id="wrapper"> <canvas id="canvas"></canvas> <ul id="results"></ul> <span id="confirm">確定</span> <p id="subtotal">0円</p> </div> <script> // DOM取得とQRコード用のビデオ設定 const video = document.createElement("video"); const canvasElement = document.getElementById("canvas"); const canvas = canvasElement.getContext("2d"); const results = document.getElementById("results"); const subtotal = document.getElementById("subtotal"); const confirm = document.getElementById("confirm"); let item_dict = {}; // カウンターで商品の連続読み取りを阻止 let next_counter = 0; // ここでjsにデータを送る関数を設定 confirm.addEventListener("click", send_data); // Use facingMode: environment to attemt to get the front camera on phones navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }).then(function(stream) { video.srcObject = stream; video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen video.play(); requestAnimationFrame(tick); }); function tick() { // キャンバスにビデオ画像を反映 if (video.readyState === video.HAVE_ENOUGH_DATA) { canvasElement.height = video.videoHeight; canvasElement.width = video.videoWidth; canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height); const imageData = canvas.getImageData(0, 0, canvasElement.width, canvasElement.height); const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: "dontInvert", }); if (code) { if (Object.keys(item_dict).includes(code.data)){ // アイテムを読み取っていたら購入個数をプラスしていく if(next_counter > 50){ item_dict[code.data] += 1; results.children[Object.keys(item_dict).indexOf(code.data)].children[1].innerText = item_dict[code.data]; next_counter = 0; change_subtotal(); } } else if (code.data != "") { // 初めて読み込む商品(QRコード)なら画面に表示+内部でdictに追加 const child = document.createElement("li"); const name = document.createElement("p"); const num = document.createElement("p"); const price = document.createElement("p"); const btn_minus = document.createElement("span"); const btn_plus = document.createElement("span"); const btn_delete = document.createElement("span"); btn_minus.addEventListener("click", {num: -1, data: code.data, handleEvent: change_num}); btn_plus.addEventListener("click", {num: 1, data: code.data, handleEvent: change_num}); btn_delete.addEventListener("click", {data: code.data, handleEvent: delete_elm}); name.innerText = code.data.split(",")[1]; num.innerText = 1; price.innerText = code.data.split(",")[4]; btn_minus.innerText = "-"; btn_plus.innerText = "+"; btn_delete.innerText = "x"; child.append(name); child.append(num); child.append(btn_minus); child.append(btn_plus); child.append(btn_delete); results.append(child); item_dict[code.data] = 1; next_counter = 0; change_subtotal(); } } else { next_counter++; } } requestAnimationFrame(tick); } function change_num(e) { item_dict[this.data] += this.num; e.target.parentNode.children[1].innerText = item_dict[this.data]; change_subtotal(); } function delete_elm(e) { delete item_dict[this.data]; e.target.parentNode.remove(); change_subtotal(); } function change_subtotal() { let sum = 0; Object.keys(item_dict).forEach(elm => { sum += item_dict[elm] * elm.split(",")[4]; }); subtotal.innerText = sum.toLocaleString() + "円"; } function send_data() { // ここが一番の疑問点 // データの送信はできていてdoPost側でも読み取れる(こちらから送信はできているため通信は可能) google.script.run.withSuccessHandler(e => { const URL = e; const postparam = { "method" : "POST", "mode" : "no-cors", "Content-Type" : "application/x-www-form-urlencoded", "body" : JSON.stringify(item_dict) }; const res = fetch(URL, postparam).then(response => response).then(data => { // 読み取るタイミングやfetchの使い方が曖昧なため、これで合っているかすら疑問 // オブジェクトは取得できているが処理の方法を理解できていない、、? console.log(data); }); }).get_url(); } </script> </body> </html>

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

penguin520

2023/01/26 07:32

"Response {type: 'opaque', url: '', redirected: false, status: 0, ok: false, …}" は console.log(data); で取得した値ですか? もう少しミニマムなコードを提示することはできますか?
seito

2023/01/26 13:37

response...はlogで取得した値です。 質問になれていなかったため、見ずらい質問で申し訳ありません。 上記のソースコードを自分が主要と考える部分に編集したのですが、もし主要な部分が欠落していましたら、そこも含めて教えていただけると幸いです。 お手数をお掛けしますが、何卒よろしくお願いいたします。
guest

回答1

0

ベストアンサー

doPostで返信されたhtmlをjs側で受け取り、ページ遷移をしたい。

あくまで、「ボタンを押下後に別ページに遷移したい」のであれば、UrlFetchApp を使うのではなく、たとえば form を submit する形式の方が簡単だと思います。
(GAS は iframe の関係で制限があるため、「UrlFetchApp を使ってページ内容を取得してそのページに自動的に遷移する」ということを行うのは難しいと思います。
UrlFetchApp を使うならページ遷移せず、Ajax でページを書き換えるやりかたの方をおすすめします。
ただ今回はページ遷移が要件のようですのでこちらについては触れません)


下記のコードは基本的な動作を理解していただく目的のために簡略化しています。(簡略といっても省略しているわけではなく、コピペしてデプロイすれば動作します)。実際には動作を理解した上で、質問者さんのアプリに適用してください。また、コード中のコメントが冗長になっていますが説明のためですので、御承知おきください。


👉 createTemplateFromFile

HTML 内で <?= ?> をつかっていて、その部分を任意の値に置き換えたいようなときは
createHtmlOutputFromFile ではなくて、
createTemplateFromFile を使います。

・以下は動作するサンプルコードです。ためしに新しいファイルに記述してWebアプリとしてデプロイすると動作がわかりやすいかと思います。

<コード.gs>

js

1function doGet() { 2 // テンプレートファイルを読み込む 3 const index = HtmlService.createTemplateFromFile('index'); 4 5 // テンプレートファイルに値を渡す。 6 index.value1 = 'おはよう'; 7 8 // ページを構築して返す。 9 return index.evaluate(); 10} 11 12function getMessage() { 13 return 'おやすみ'; 14}

<index.html>

html

1<!DOCTYPE html> 2<html> 3 <head> 4 <base target="_top"> 5 </head> 6 <body> 7 <h1><?= value1 ?></h1> 8 <h1><?= getMessage() ?></h1> 9</body> 10</html>

こうすることで、HTML の <?= ?> で囲まれた部分に、任意の値を渡したり、GAS側で用意した関数の計算結果を表示したりできます。
(※ただし、<?= ?>部分の置き換えはページを読み込んで構築する時だけ行われますので、たとえばページ表示後、動的に<?= ?>部分を変更するということはできません)


createTemplateFromFile 関数の基本:

  • <?= ?> が使われているHTML(テンプレートファイル)の <?= ?> の部分を、GAS 側の変数や関数の計算結果、または渡された値に置き換えて、ページを構築します。
  • ページを返すときに、evaluate() 関数を使う必要があります。

👉 formdata イベントを利用して、フォーム送信時にデータを追加する。

ではフォームの submit(=送信時)に任意のデータ(今回でいえば領収書内に記載する金額やユーザー情報等のデータ)を渡すにはどうすればよいでしょうか。

これには複数のやり方がありますが、formdata イベントを利用してみるのはいかがでしょうか。
(注:質問文記載のアプリの動作を考察すると、ユーザーに文字を手入力させる要素がなく、特に入力内容のチェックも不要であるようなので、その前提でこの方法を採用しています。仮に入力値をチェック(バリデーション)する必要があるような場合は、別の方法になります)

具体的には、formdata イベントのイベントハンドラー内に「送信したいデータをフォームデータに追加する処理」を記述する、ということです。

といわれても意味不明と思われるかもしれませんので、コードで示します。

<コード.gs>

js

1/* 初期ページ表示時に呼ばれる */ 2function doGet(e) { 3 // テンプレートファイルを読み込む 4 var index = HtmlService.createTemplateFromFile("index"); 5 // WebアプリのURLを index.html に渡す。 6 index.url = ScriptApp.getService().getUrl(); 7 8 // ページを構築して返す 9 return index.evaluate(); 10} 11 12/* フォーム送信時に呼ばれる */ 13function doPost(e) { 14 // フォームから受け取ったデータ(JSONフォーマットの文字列)を取出す。 15 const item_dict_json = e.parameter.item_dict; 16 17 // JSONフォ-マットの文字列を、オブジェクトに戻す。 18 const item_dict = JSON.parse(item_dict_json); 19 20 // テンプレートファイルを読み込む 21 const result = HtmlService.createTemplateFromFile("result"); 22 23 // フォームから受け取ったデータを result.html に渡す。 24 result.date = item_dict.date; 25 result.address = item_dict.address; 26 27 // ページを構築して返す 28 return result.evaluate(); 29}

<index.html>

html

1<!DOCTYPE html> 2<html> 3 4<head> 5 <base target="_top"> 6 <meta charset="UTF-8"> 7 <title>フォーム送信サンプル by Qnoir</title> 8</head> 9 10<body> 11 <form id="form" method="POST" action="<?= url ?>" target="_top"> 12 <button type="submit">送信</button> 13 </form> 14 15 <script> 16 // 送信用のデータ。 17 let item_dict = { 18 date: '2023/01/01', 19 address: '東京都新宿区○ ○', 20 }; 21 22 // フォーム送信時に発生する 'formdata' イベントを処理する関数(イベントハンドラー)を実装 23 document.getElementById('form').addEventListener('formdata', (e) => { 24 // フォームデ-タを格納するオブジェクト(FormDataオブジェクト)への参照を取得 25 const fd = e.formData; 26 27 // フォーム送信先(result.html)に送信するデータをセット. 28 fd.set('item_dict', JSON.stringify(item_dict)); 29 }); 30 </script> 31</body> 32 33</html>

<result.html>

html

1<!DOCTYPE html> 2<!-- 返却用html --> 3<html> 4 5<head> 6 <base target="_top"> 7</head> 8 9<body> 10 <div> 11 日付 <?= date ?></p> 12 </div> 13 <div> 14 住所 <?= address ?></p> 15 </div> 16 17</body> 18 19</html>

流れとしては以下のようになります。
(1)GASでデプロイして発行したURLをブラウザに入力すると、コード.gs の doGet関数が呼ばれ、index.html が返されます。
このとき、index.html内の form要素の <?= url ?> の部分を、GAS Web アプリのデプロイURL に置き換えています。

なぜこのようにするかといえば、フォーム送信時に、form 要素の action 属性に指定した URL に対してフォームデータが POST されるためです。
(こうすることで、フォームデータを GAS のデプロイURLで受け取れる)


(2)「送信」ボタンを押すと、フォームデータが form 要素の action 属性に指定した URL に対して送信されます。
このとき、「formdata イベント」が発生し、index.html の<script>タグ内に記述した「イベントハンドラー」が実行されます。
「イベントハンドラー」というのは、「イベントを処理するための関数」という意味で、具体的には下記の 「(e) => { 」以降の部分です。

この関数の中で、送信するデータ(item_dict)をセットしています。
複数のオブジェクトを送るため、JSON.stringfy でJSON文字列に変換しています。

js

1 // フォーム送信時に発生する 'formdata' イベントを処理する関数(イベントハンドラー)を実装 2 document.getElementById('form').addEventListener('formdata', (e) => { 3 // フォームデ-タを格納するオブジェクト(FormDataオブジェクト)への参照を取得 4 const fd = e.formData; 5 6 // フォーム送信先(コード.gs の doPost 関数)に送信するデータをセット 7 fd.set('item_dict', JSON.stringify(item_dict)); 8 });



(3)フォームを送信すると、(form 要素の action 属性に指定した URL に基づき) GAS 側の doPost 関数が実行されます。

js

1/* フォーム送信時に呼ばれる */ 2function doPost(e) { 3 // フォームから受け取ったデータ(JSONフォーマットの文字列)を取り出す。 4 const item_dict_json = e.parameter.item_dict; 5 6 // JSONフォーマットの文字列を、オブジェクトに戻す。 7 const item_dict = JSON.parse(item_dict_json); 8 以下略

doPost関数の引数 「e」には、先ほどのイベントハンドラー内でセットしたフォームデータが含まれています。
これを取り出すには e.parameter.変数名 というようにします。

取り出されるのは、JSON.stgringify によって JSON 文字列に変換されたデータなので、これをオブジェクトにもどすために JSON.parse しています。



(4)あとは1.と同様に、返却用のページに値を渡して、evaluate でページを構築して result.html を表示する、という流れです。

投稿2023/01/27 15:11

編集2023/01/29 10:13
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

seito

2023/01/30 14:45

createTemplateFromFileの変更と、formを利用したデータやり取りをしてみたら、想像していた動きを作ることができました!!! gasの開発が初だったので、createTempalteFromFileの使用などを詳しく知りませんでした、、助かりました! とてもわかり易くご回答して下さりありがとうございます。 ベストアンサーにさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問