参考
Last active
December 21, 2019 05:31
-
-
Save hyt48/3fae366bd5a884c91965090017fa46bd to your computer and use it in GitHub Desktop.
gas-vue-todo-webapp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function doGet() { | |
var htmlOutput = HtmlService.createTemplateFromFile('index').evaluate(); | |
htmlOutput | |
.setTitle('GAS + Vue.js App') | |
.addMetaTag('viewport', 'width=device-width, initial-scale=1'); | |
return htmlOutput; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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