GASでspreadsheetに追記できるwebアプリを作ってみた。
Webアプリからspreadsheetにデータを送りたかったんですが、大変でした。
TL;DR
簡単にwebappを作るなら
- SpreadSheetからgoogle apps scriptを生成する
- VueCLI3でプロジェクト作成し、vue.config.jsをいじる
- claspインストールし、2で作成したプロジェクトの成果物をpushできるようにする
- POSTで書き込みをしようとするのではなく、
google.script.run.○○
で実行する
解説
1. SpreadSheetからgoogle apps scriptを生成する
SpreadSheetからgasを作成します。 この際gasとsheetを分けても問題ないが、呼び出しが少し面倒になるため、個人的にはsheetから生成したほうがいいと思っています。
2. VueCLI3でプロジェクト作成し、vue.config.jsをいじる
vue create gasapp cd gasapp npm i -D html-webpack-inline-source-plugin webpack-cdn-plugin
プロジェクトを作成し、以下のvue.config.jsで使うためのモジュールをinstall。 cli.vuejs.org
// vue.config.js module.exports = { chainWebpack: config => { // disable prefetch and preload config.plugins.delete("prefetch"); config.plugins.delete("preload"); // Make js and css files inline into index.html config .plugin("html-inline-source") .use(require("html-webpack-inline-source-plugin")); config.plugin("html").tap(args => { args[0].inlineSource = "(/css/.+\\.css|/js/.+\\.js)"; return args; }); // make inline images config.module .rule("images") .use("url-loader") .options({}); // make inline media config.module .rule("media") .use("url-loader") .options({}); // make inline fonts config.module .rule("fonts") .use("url-loader") .options({}); // make inline svg config.module .rule("svg") .uses.delete("file-loader") .end() .use("url-loader") .loader("url-loader") .options({}); // Get npm modules from CDN config.plugin("webpack-cdn").use(require("webpack-cdn-plugin"), [ { modules: [ { name: "vue", var: "Vue", path: "dist/vue.runtime.min.js" }, { name: "vue-router", var: "VueRouter", path: "dist/vue-router.min.js" } ] } ]); if (process.env.NODE_ENV === "production") { // html minify settings for GAS config.plugin("html").tap(args => { args[0].minify.removeAttributeQuotes = false; args[0].minify.removeScriptTypeAttributes = false; return args; }); } } };
こちらに記載されていたもののパクりです。 とても感謝してます。
これにより、vueで書いてもgas上で動かせるようになりました。
claspインストールし、2で作成したプロジェクトの成果物をpushできるようにする
こちらを参考にclaspを使えるようにした。
また、こちらを参考に appsscript.json
等々を配置
package.json
の script
に "push": "vue-cli-service build && clasp push -f"
を追記してdist以下の生成とそれらのpushをひとまとめにするとラクでした。
4.POSTで書き込みをしようとするのではなく、 google.script.run.○○
で実行する
一番大変なところでした。 詳細は下に記載するとして、まず結論としてページのview部分のファイルはこのようになりました。 *CSSフレームワークにbulmaを使用してます
// index.vue <template> <div class="container"> <h1>Input Your Data</h1> <div class="field"> <div class="control"> <input class="input is-info" type="text" placeholder="Text" v-model="text"> </div> </div> <div class="field"> <div class="control"> <a class="button is-link" @click="onClick">Submit</a> </div> </div> </div> </template> <script> export default { name: "Index", data() { return { text: "" }; }, methods: { onClick: function(e) { google.script.run.addData( JSON.stringify({ text: this.text, }) ); } }; </script>
Code.js(google apps script)
function doGet() { return HtmlService.createHtmlOutputFromFile("index").addMetaTag( "viewport", "width=device-width, initial-scale=1" ); } function addData(rawParams) { Logger.log("addData"); var params = JSON.parse(rawParams); var ss = SpreadsheetApp.getActive(); var sheet = ss.getActiveSheet(); var values = [params.text]; sheet.appendRow(values); }
どうにかしてPOSTでできないかと考えていましたが、普通に google.script.run.hogehoge
とやったら動きました。
詰まったところ : POST編
詰まった要因として一番大きかったところはPOSTでリクエストを飛ばしてどうにかできないかと考えていたところでした。
一番最初に考えたこと
axiosを入れて普通にpostしようとした
axios.post("https://script.google.com/macros/s/AKfxxxxx/exec", params)
->405エラー OPTIONSのmethodで飛ばされてしまっているらしい。
次に考えたこと
それだと通らないようだったので application/x-www-form-urlencoded
で投げるように修正してみました。
const params = new URLSearchParams(); params.append("test", this.test); axios.post("https://script.google.com/macros/s/AKfxxxxx/exec", params)
↓
Access to XMLHttpRequest at 'https://script.google.com/macros/s/AKfxxxxx/exec' from origin 'https://n-hv3nxxxxx-script.googleusercontent.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
???
どうやらoriginが違う・・・
ブラウザでアクセスしているURLも https://script.google.com/macros/s/AKfxxxxx/exec
にもかかわらず、originが違う認識になっている。
デベロッパーツール開いて window.location.href
等でURLを見てみても https://script.google.com/macros/s/AKfxxxxx/exec
になっているのでなかなか不思議な現象。
きっとセキュリティ的な問題でこういう仕様になっているのだろうと諦めた。
結果としておかげさまで google.script.run
にたどりつけたのでよかったといえばよかった。
そして公式のドキュメントにも実はちゃんと書いていた。
詰まったところ : Lambda編
ブラウザから叩こうとするからCORSで阻まれるので、何らかの形でサーバー側から実行できたらいいじゃんと考え、なんとなくlambdaでできないかと試してみた。
紆余曲折はあったものの、上記を参考にして一旦動くようにはなったが、定期的にtokenのリフレッシュをしなければいけない、というところで少し面倒+そこまでして動かしたいものではないなということで断念。
その後いろいろ試してみたら
// Lambda const request = require("request"); const headers = { "Content-type": "application/json" }; const dataString = '{"value":"aaa"}'; var options = { url:process.env['url'], method: "POST", headers: headers, body: dataString }; function callback(error, response, body) { if (!error && response.statusCode == 200) { console.log(body); const response = { statusCode: 200, body: JSON.stringify("Hello from Lambda!") }; return response; }else{ console.error(error) } } exports.handler = event => { console.log("POST gas to sheet"); request(options, callback); console.log("Finished"); // TODO implement };
だいぶてきとーに書いてますが、普通に request
で飛ばしたらいけました。
tokenとか全然関係ないのか・・・
余談
実装してみたサンプルコード
jsでパスワードのvalidationのようなものを書いてみた
ちょっとしたvalidationのようなものを書いてみたので、備忘録として残しておきたい。
前提
HTMLはシンプルに
<input type="password" id="passwordInput"/>
こんなかんじの想定です。
あとjs上での下処理は
let passwordText = document.getElementById('passwordInput').value;
とします。
文字数の確認
passwordの文字が 8文字以上
で良しとしたい場合の想定です。
これはシンプルにlengthで取れますね
if(passwordText.length < 8){ // 何らかのエラー処理 }else{ // 正常時の処理 }
HTMLタグのmaxlength
そういえばHTML5の仕様でタグ上に記載することができますね。
<input type="password" id="passwordInput" maxlength="8"/>
こうすることでjs側での処理は不要になりますね。
同じ文字がいないかどうか
aa
や 11
みたいに連続した文字がいた場合にエラーとしたいときのロジックです。
let passwordTexts = passwordInput.split(''); let previousCharacter = ''; let conuter = 1; let hasSameCharacter = passwordTexts.some((character) => { if(character === previousCharacter){ conuter++; }else{ previousCharacter = character; conuter = 1; } return conuter === 2 }); if(hasSameCharacter){ // エラー処理 }
特定の文字入力があるかどうかの判定
今回の想定では英字と数字、どちらも入ってるかどうかチェックするというものです。
let strMutcher = passwordText.match(/[a-z]/gi); let numMutcher = passwordText.match(/[0-9]/gi); if(strMutcher === null || numMutcher === null){ // エラー時処理等 }
match
でokだった場合は配列が返ってくるので、できれば .length > 0
あたりで判定したいところなのですが、
NGだった場合にnullしか返してこないので上記のような書き方をしました。
(個人的にもう少し改良の余地はあるように思える)
(正規表現がちょっと適切ではない気がするので、もっと勉強が必要)
HTMLタグで正規表現
<input type="password" pattern="^[0-9A-Za-z]+$">
と、いったかんじでタグ上でも正規表現を用いてチェックすることができるようです。 www.htmq.com
ちょっとしたポップアップも出て、便利ですね。
ただしこれ、formタグとセットで使わなければいけないので(ポップアップが出るのもきっとsubmitされるとき)
ページの作り方によっては使いにくいかもしれません。
余談
HTMLやjsでvalidationをする方法を上に書きましたが、結局クライアント側ではどうとでもできるので、本格的な処理はやはりjsで書かなければいけませんね。
あとはHTMLとjs、双方向からできるにしても、どの処理がどっちで担当しているか、というのがごちゃ混ぜになると管理が大変になると思うので、 個人的にはある程度jsに寄せたいなと思う派です。
以上、備忘録でした
Vue CLI3のproductionでconsole.logを消した方法
やりたかったこと
本題の通りなのですが、productionの状態でbuildしたソースからよろしくconsole.logを消せないかなと思ったのでいろいろ調べてみました。
結論
uglifyjs-webpack-plugin
を入れて、vue.config.jsにconsole.logを消す設定を書く。
pluginのインストール
npm i -D uglifyjs-webpack-plugin
vue.config.jsに書き足す
const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const isProd = process.env.NODE_ENV === "production"; module.exports = { configureWebpack: { optimization: { minimizer: isProd ? [ new UglifyJsPlugin({ uglifyOptions: { compress: { drop_console: true }, } }) ] : [] } } }
こちらで発見しました
所感
これを試したのは個人的につくった実装物なので、一旦これでいいんですが、もう少し大きい実装で、コードを難読化したら発生したbugとかに関してはlogがでなくてちょっとつらいのかなと思いました。
もう少しいろいろ設定ができそうなので、折を見て試して追記できたらと思います
IEでreadonlyのinputにcssを当てたくなった時の対処
日々大きな壁として立ちはだかるIEくんに対して頑張ったお話のひとつです。
よくある実装のひとつだと思うのですが、そのinputがreadonlyだったときに、ぱっと見でreadonlyだと識別できるようにスタイルを変えたいというところの実装をしていました。
結論
input[readonly] { background-color: gray }
で対処できました。
(正直当たり前といえば当たり前なのですが)
もともとやっていたこと
input:read-only { background-color: gray }
Chromeだとこれで動いていたんですよね。
そしていざIEで確認してみたところ、ちゃんと効いてない。
調べてみたところ、 :read-only
の擬似クラスはIEではサポートされてないとのことでした。
(なるほどそれは動かない 笑)
興味深かったところ
input[type="text"][readonly] { background-color: gray }
といった形で [ ]
を2つつなげても動いた・・・
まあ確かに
input:read-only.hoge-class { background-color: gray }
みたいな形でつなげても動作するので、こちらも当たり前といえば当たり前か・・・(CSSのセレクターももう少し勉強が必要ですね)
いつまで動いてるかわかりませんが、下記のjsFiddleは実験物
はじめてのレポジトリにgit pushするときのお作法
やりたいこと
gitでレポジトリ作って、ローカルでひとまずの実装をしたあとにgitにあげる
なんだかんだ頻度がそんなに高くないので忘れてしまう・・・
0. gitのレポジトリつくり
割愛。
そういえばgithubもprivateのレポジトリがタダで使えるようになったのはありがたい。
1. git init
ローカルのリポジトリとして機能させるために
git init
その後適当に実装
2. add と commit
git add . git commit -m "messages"
で修正をcommit作成
3. remote add
git remote add origin git@github.com:....
でremoteに登録
4. push
あとは
git push
ついでに
別ブランチきって最初にpushしようとするとそのときもまた怒られる。 その際は
git push --set-upstream origin branch-name
でリモートブランチをつくる
gitの関連書籍
正直基本的な操作はぐぐったらすぐ出てくるので本読んでまでどうにかするものでもない気がしている。(もちろんちゃんと学ぶことは重要)
下の本は個人的には読みやすかったが SourceTree
ベースでgitの操作が進んでいたので、コマンドラインでの操作について学びたかったときには少し違った。
www.amazon.co.jp
こちらについては結構詳細に書いてあったのでよかった印象 www.amazon.co.jp