1. hyt48

    No comment

    hyt48
Changes in body
Source | HTML | Preview
@@ -1,367 +1,367 @@
こんにちは、otkと申します。Qiita初投稿です。よろしくお願いいたします。
## はじめに
最近Google Apps Script(GAS)を触る機会あり、今までほとんど触ったことがなかったのですが、使ってみてかなりいろいろできることに驚きました。その中でGASでWebアプリをつくれるということを知ったので、かなり初歩的な内容だとは思いますが、それについて書いていこうと思います。
## つくるもの
定番のTodoアプリです。
-[こちら](https://cr-vue.mio3io.com/tutorials/todo.html)のチュートリアルのコードを使わせていただきました。(CDN版のVue.jsを使っています。)チュートリアルではデータをローカルストレージに保存していますが、最後にGoogleスプレッドシートを簡易DBとして使用し、そちらに保存するよう変更してます。
+[こちら](https://cr-vue.mio3io.com/tutorials/todo.html)のチュートリアルのコードを使わせていただきました。(CDN版のVue.jsを使っています。)チュートリアルではデータをローカルストレージに保存していますが、いちばん最後でGoogleスプレッドシートに保存するよう変更していきます。
## 必要なもの
- [Googleアカウント](https://support.google.com/accounts/answer/27441?hl=ja)だけあればOKです。
## 手順
以下手順で進めます。
1. GASプロジェクトを作成。
2. GASでGETリクエストを受け取り、HTMLを表示するプログラムを書く。
3. Webアプリ公開・動作確認。
4. CSS、JSを別ファイルに分割してTodoアプリ作成。
5. データをGoogleスプレッドシートに保存するように変更。
### 1. GASプロジェクトを作成。
https://script.google.com/ または Gooleドライブ からGASプロジェクトを作成します。
<img width="1920" alt="スクリーンショット 2019-12-18 19.42.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/f6fc9921-fef1-9d2e-7b71-c333b043db96.png">
### 2. GASでGETリクエストを受け取り、HTMLを表示するプログラムを書く。
サーバー側GASを記述します。
GASでGETリクエストを受け取るようにするためには[HTML Service](https://developers.google.com/apps-script/guides/html)の`doGet`関数を使います。以下ではGETリクエストを受け取ったときに`index.html`ファイルの内容を表示するようにしています。
```js:Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile('index');
}
```
次にフロント側のHTMLです。
[ファイル] > [New] > [HTMLファイル]で`index.html`ファイルを作成できます。
```html:index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
Hello!
</body>
</html>
```
### 3. Webアプリ公開・動作確認。
これだけで最低限の準備ができたので、公開して動作確認します。
[公開] > [ウェブアプリケーションとして導入]。
<img width="1272" alt="スクリーンショット 2019-12-18 20.54.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/7a5bcaaf-6c8c-7977-9900-3671bd96cc6a.png">
以下のようにして [Deploy]。
<img width="1276" alt="スクリーンショット 2019-12-18 20.55.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/2f2605b9-4397-c187-989b-f6754daed107.png">
URLが表示されるので、そちらにアクセスすれば`index.html`の内容が表示されるはずです。これで一応Webアプリを公開することができました。
<img width="1276" alt="スクリーンショット 2019-12-18 20.56.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/a6644fb5-2a12-8818-35a0-98de251f3d9b.png">
<img width="1414" alt="スクリーンショット 2019-12-18 20.56.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/2ae209fd-d004-976c-b2fd-3ab0e3dfd598.png">
### 4. CSS、JSを別ファイルに分割する。
Todoアプリを作っていきます。
こちらの[チュートリアルの完成形](https://cr-vue.mio3io.com/tutorials/todo.html#%E5%AE%8C%E6%88%90%E5%BD%A2)の`index.html`, `main.css`, `main.js`を書いていきたいのですが、GASではHTMLファイルとGSファイルのみしか作成できません。ですので`<style>`タグと`<script>`タグを使って全てHTMLファイルに記述していきます。
ファイルは分割したいので、`main.css.html`と`main.js.html`ファイルを作成し、それらを`index.html`ファイルで読み込むようにします。
```html:main.css.html
<style>
* {
box-sizing: border-box;
}
#app {
max-width: 640px;
margin: 0 auto;
}
/* 〜〜〜 中略 〜〜〜 */
button {
border: none;
border-radius: 20px;
line-height: 24px;
padding: 0 8px;
background: #0099e4;
color: #fff;
cursor: pointer;
}
</style>
```
```html:main.js.html
<!-- CDN版Vue.js読み込み -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<!-- ローカルストレージに保存するための処理 -->
<script>
// ★STEP2
// https://jp.vuejs.org/v2/examples/todomvc.html
var STORAGE_KEY = 'todos-vuejs-demo';
var todoStorage = {
fetch: function() {
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
todos.forEach(function(todo, index) {
todo.id = index;
});
todoStorage.uid = todos.length;
return todos;
},
save: function(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
},
};
</script>
<!-- Vueインスタンス -->
<script>
// ★STEP1
const app = new Vue({
el: '#app',
/* 〜〜〜 中略 〜〜〜 */
// ★STEP10 削除の処理
doRemove: function(item) {
var index = this.todos.indexOf(item);
this.todos.splice(index, 1);
},
},
});
</script>
```
`index.html`で読み込ませるためには以下のように記述します。
```html:index.html
...
<head>
...
<?!= HtmlService.createHtmlOutputFromFile('main.css').getContent(); ?>
</head>
...
<body>
<?!= HtmlService.createHtmlOutputFromFile('main.js').getContent(); ?>
</body>
```
`<?!=`と`?>`の間にGASのコードを記述して処理を実行することができるので、上のように記述することで別ファイルの`main.css.html`, `main.js.html`ファイルを読み込むことができます。
あとは[チュートリアルのコード](https://github.com/mio3io/cr-vue/tree/master/codes/tutorial-todo)と同じです。
以上でTodoアプリを作成・デプロイすることができます。簡単!
<img width="1920" alt="スクリーンショット 2019-12-19 6.35.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/e05f5212-6bf6-0345-9814-0064d074f18a.png">
### 5. データをGoogleスプレッドシートに保存するように変更。
最後に、ローカルストレージに保存していたデータを、GoogleスプレッドシートをDBのように使ってそちらに保存したいと思います。
以下のようなスプレッドシートを用意します。
<img width="1220" alt="スクリーンショット 2019-12-19 6.41.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/181876/19f82f37-a945-f56e-791a-25e02d5fcf95.png">
こちらをGASで取得する処理は以下になります。
```js:db.gs
var DB_SPREADSHEET_URL = 'スプレッドシートのURL';
var ss = SpreadsheetApp.openByUrl(DB_SPREADSHEET_URL);
var sheet = ss.getSheetByName('Todo');
// スプレッドシートからデータを読み込む処理。
function getTodosFromDb() {
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var dataObjects = [];
var data2dArray = sheet.getRange(1, 1, lastRow, lastCol).getValues();
var keys = data2dArray[0];
for (var row = 1; row < lastRow; row += 1) {
var dataRow = data2dArray[row];
var dataObject = toObject(dataRow, keys);
dataObjects.push(dataObject);
}
return dataObjects;
}
// スプレッドシートにデータを書き込む処理。
function setTodosToDb(todos) {
sheet.clear();
var keys = Object.keys(todos[0]);
var data2dArray = [keys];
for (var i = 0; i < todos.length; i += 1) {
var dataObject = todos[i];
var dataRow = toArray(dataObject, keys);
data2dArray.push(dataRow);
}
var numRows = data2dArray.length;
var numCols = keys.length;
sheet.getRange(1, 1, numRows, numCols).setValues(data2dArray);
}
function toObject(array, keys) {
var obj = {};
for (var i = 0; i < keys.length; i += 1) {
obj[keys[i]] = array[i];
}
return obj;
}
function toArray(obj, keys) {
var array = [];
for (var i = 0; i < keys.length; i += 1) {
array.push(obj[keys[i]]);
}
return array;
}
```
こちらの`getTodosFromDb`メソッドと`setTodosToDb`メソッドをフロント側から呼び出したいのですが、それには`google.script.run`というAPIを使います。
https://developers.google.com/apps-script/guides/html/reference/run?hl=en
この`google.script.run`が少し複雑ですので、以下の例で説明します。例では以下のような処理を行なっています。
- サーバー側で定義されたdoSomethingメソッド(①)をフロント側で`google.script.run`で呼び出しています(③)。
- doSomethingを実行し、処理が成功するとonSuccessメソッド(②)が実行されます。(失敗した場合はonFailureメソッドが実行されます。)
- このときdoSomethingの戻り値がonSuccessの引数doSomethingReturnValueに渡されます。
```js:GASサーバー側
// ①
function doSomething(arg) {
...
return returnValue;
}
```
```html:HTMLフロント側
<script>
...
// ②
function onSuccess(doSomethingReturnValue) {
// 何か処理
}
...
// ③
google.script.run
.withSuccessHandler(onSuccess)
.withFailureHandler(onFailure)
.doSomething('hello');
...
</script>
```
こんな感じで、このままだと少し使いにくいので、[こちら](https://gist.github.com/torufurukawa/64339baf16efd3598e71dd763d1db0cf#file-google-script-run-promise-js-L11)を参考に、async/awaitが使えるように以下の処理を定義します。
```js:HTMLフロント側
function scriptRunPromise() {
const gs = {};
// google.script.run contains doSomething() methods at runtime.
// Object.keys(goog.sscript.run) returns array of method names.
const keys = Object.keys(google.script.run);
// for each key, i.e. method name...
for (let i = 0; i < keys.length; i++) {
// assign the function to gs.doSomething() which returns...
gs[keys[i]] = (function(key) {
// a function which accepts arbitrary args and returns...
return function(...args) {
// a promise that executes ...
return new Promise(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
[key].apply(google.script.run, args);
});
};
})(keys[i]);
}
return gs;
}
```
こうすることで以下のような形でサーバー側のdoSomethingメソッドが呼び出せるようになります。
```js:HTMLフロント側
var gs = scriptRunPromise();
var doSomethingReturnValue = await gs.doSomething('hello');
```
これを使ってチュートリアルのローカルストレージへのデータ保存部分を以下のように書き換えます。
```html:main.js.html(変更前)
<script>
var STORAGE_KEY = 'todos-vuejs-demo';
var todoStorage = {
fetch: function() {
var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
todos.forEach(function(todo, index) {
todo.id = index;
});
todoStorage.uid = todos.length;
return todos;
},
save: function(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
},
};
</script>
```
 ⬇️
```html:main.js.html(変更後)
<script>
var db = {
fetch: async function() {
var todos = await fetchTodos();
todos.forEach(function(todo, index) {
todo.id = index;
});
db.uid = todos.length;
return todos;
},
save: async function(todos) {
await saveTodos(todos);
},
};
function fetchTodos() {
const gs = scriptRunPromise();
return gs.getTodosFromDb();
}
function saveTodos(todos) {
const gs = scriptRunPromise();
gs.setTodosToDb(todos);
}
</script>
```
残りは`fetch`, `save`メソッドの呼び出し部分を修正すればOKです。
最終的なコードは[こちら](https://gist.github.com/hyt48/3fae366bd5a884c91965090017fa46bd)です。
## おわりに
以上がGASでのWebアプリの作成方法になります。
簡単に、なにより無料で使えるのが便利ですよね。
[clasp](https://github.com/google/clasp)というGASをローカルで開発するためのツールがあるので、もっと凝ったことをしたい場合はこれを使うと便利です。
他にも色々便利な使い方があるようなので、色々やってみて、またアウトプットしていきたいと思います。
読んでいただきありがとうございました!