Skip to content

Instantly share code, notes, and snippets.

@hyt48
Last active December 21, 2019 05:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hyt48/3fae366bd5a884c91965090017fa46bd to your computer and use it in GitHub Desktop.
Save hyt48/3fae366bd5a884c91965090017fa46bd to your computer and use it in GitHub Desktop.
gas-vue-todo-webapp
function doGet() {
var htmlOutput = HtmlService.createTemplateFromFile('index').evaluate();
htmlOutput
.setTitle('GAS + Vue.js App')
.addMetaTag('viewport', 'width=device-width, initial-scale=1');
return htmlOutput;
}
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 data2dArray = sheet.getRange(1, 1, lastRow, lastCol).getValues();
var keys = data2dArray[0];
var dataObjects = [];
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;
}
<!DOCTYPE html>
<html>
<head>
<base target="_top" />
<?!= HtmlService.createHtmlOutputFromFile('main.css').getContent(); ?>
</head>
<body>
<div id="app">
<h1>チュートリアルToDoリスト</h1>
<!-- ★STEP11 -->
<label v-for="label in options">
<input type="radio" v-model="current" v-bind:value="label.value" />{{ label.label }}
</label>
<!-- ★STEP12 -->
({{ computedTodos.length }} 件を表示)
<!-- ★STEP4 リスト用テーブル -->
<table>
<thead v-pre>
<tr>
<th class="id">ID</th>
<th class="comment">コメント</th>
<th class="state">状態</th>
<th class="button">-</th>
</tr>
</thead>
<tbody>
<!-- ★STEP5 ToDo の要素をループ -->
<tr v-for="item in computedTodos" v-bind:key="item.id" v-bind:class="{done:item.state}">
<th>{{ item.id }}</th>
<td>{{ item.comment }}</td>
<td class="state">
<!-- ★STEP10 状態変更ボタン -->
<button v-on:click="doChangeState(item)">
{{ labels[item.state] }}
</button>
</td>
<td class="button">
<!-- ★STEP10 削除ボタン -->
<button v-on:click.shift="doRemove(item)">
削除
</button>
</td>
</tr>
</tbody>
</table>
<p>※削除ボタンはシフトキーを押しながらクリックして下さい</p>
<!-- ★STEP6 -->
<h2>新しい作業の追加</h2>
<form class="add-form" v-on:submit.prevent="doAdd">
<!-- コメント入力フォーム -->
コメント <input type="text" ref="comment" />
<!-- 追加ボタンのモック -->
<button type="submit">追加</button>
</form>
</div>
<?!= HtmlService.createHtmlOutputFromFile('main.js').getContent(); ?>
</body>
</html>
<style>
* {
box-sizing: border-box;
}
#app {
max-width: 640px;
margin: 0 auto;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
border-bottom: 2px solid #0099e4; /*#d31c4a */
color: #0099e4;
}
th,
th {
padding: 0 8px;
line-height: 40px;
}
thead th.id {
width: 50px;
}
thead th.state {
width: 100px;
}
thead th.button {
width: 60px;
}
tbody td.button,
tbody td.state {
text-align: center;
}
tbody tr td,
tbody tr th {
border-bottom: 1px solid #ccc;
transition: all 0.4s;
}
tbody tr.done td,
tbody tr.done th {
background: #f8f8f8;
color: #bbb;
}
tbody tr:hover td,
tbody tr:hover th {
background: #f4fbff;
}
button {
border: none;
border-radius: 20px;
line-height: 24px;
padding: 0 8px;
background: #0099e4;
color: #fff;
cursor: pointer;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
<script>
// google.script.run を async/await で使うための関数。
// 参考: https://gist.github.com/torufurukawa/64339baf16efd3598e71dd763d1db0cf
function scriptRunPromise() {
const gs = {};
const keys = Object.keys(google.script.run);
for (let i = 0; i < keys.length; i++) {
gs[keys[i]] = (function(key) {
return function(...args) {
return new Promise(function(resolve, reject) {
google.script.run
.withSuccessHandler(resolve)
.withFailureHandler(reject)
[key].apply(google.script.run, args);
});
};
})(keys[i]);
}
return gs;
}
</script>
<script>
var gs = scriptRunPromise();
var db = {
fetch: async function() {
var todos = await gs.getTodosFromDb();
console.log({ todos });
todos.forEach(function(todo, index) {
todo.id = index;
});
db.uid = todos.length;
return todos;
},
save: async function(todos) {
await gs.setTodosToDb(todos);
},
};
</script>
<script>
// ★STEP1
const app = new Vue({
el: '#app',
data: {
// ★STEP5 localStorage から 取得した ToDo のリスト
todos: [],
// ★STEP11 抽出しているToDoの状態
current: -1,
// ★STEP11&STEP13 各状態のラベル
options: [
{ value: -1, label: 'すべて' },
{ value: 0, label: '作業中' },
{ value: 1, label: '完了' },
],
},
computed: {
// ★STEP12
computedTodos: function() {
return this.todos.filter(function(el) {
return this.current < 0 ? true : this.current === el.state;
}, this);
},
// ★STEP13 作業中・完了のラベルを表示する
labels() {
return this.options.reduce(function(a, b) {
return Object.assign(a, { [b.value]: b.label });
}, {});
// キーから見つけやすいように、次のように加工したデータを作成
// {0: '作業中', 1: '完了', -1: 'すべて'}
},
},
// ★STEP8
watch: {
// オプションを使う場合はオブジェクト形式にする
todos: {
// 引数はウォッチしているプロパティの変更後の値
handler: async function(todos) {
await db.save(todos);
},
// deep オプションでネストしているデータも監視できる
deep: true,
},
},
// ★STEP9
async created() {
// インスタンス作成時に自動的に fetch() する
this.todos = await db.fetch();
},
methods: {
// ★STEP7 ToDo 追加の処理
doAdd: function(event, value) {
// ref で名前を付けておいた要素を参照
var comment = this.$refs.comment;
// 入力がなければ何もしないで return
if (!comment.value.length) {
return;
}
// { 新しいID, コメント, 作業状態 }
// というオブジェクトを現在の todos リストへ push
// 作業状態「state」はデフォルト「作業中=0」で作成
this.todos.push({
id: db.uid++,
comment: comment.value,
state: 0,
});
// フォーム要素を空にする
comment.value = '';
},
// ★STEP10 状態変更の処理
doChangeState: function(item) {
item.state = !item.state ? 1 : 0;
},
// ★STEP10 削除の処理
doRemove: function(item) {
var index = this.todos.indexOf(item);
this.todos.splice(index, 1);
},
},
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment