*Untuk Mendapatkan Full Script tanpa password silahkan Klik Disini*
Cara Membuat Website Dengan Google Script
1. Siapkan file yang akan ditampilkan (Contoh : Spreadsheet, GoogleDoc, dsb)
2. Copy Spreadsheets (Klik Disini)
3. Buka Apps Script , Ekstensi - Apps Script
4. Setelah lembar kerja Apps Script terbuka, maka akan terlihat beberapa file .html dan 1 file .gs
Copy Script dibawah ini untuk mengisi setiap file diatas.
Dan perhatikan ada beberapa scrip yang harus di sesuikan.
backend.gs
Copy script dibawah ini pada backend.gs
Masukkan Password Untuk Melihat Script (Password ada di dalam video)
const CONFIG = {
INDEX: "index.html",
NAME: "Dashboard",
DB_ID: "1BfS6GCnVKcaXiYHcMHm79q0vjQeShxp5H4K3Qtrwt8A",
SHEET_NAME: {
USERS: "users"
},
CACHE_EXPIRED_IN_SECONDS: 21600, // 6 hours
}
class App {
constructor() {
this.db = SpreadsheetApp.openById(CONFIG.DB_ID)
this.cache = CacheService.getScriptCache()
}
createToken(user) {
const token = Utilities.getUuid()
this.cache.put(token, user.email, CONFIG.CACHE_EXPIRED_IN_SECONDS)
return token
}
createItemObject(keys, values) {
const item = {}
keys.forEach((key, index) => item[key] = values[index])
return item
}
validateToken(token) {
const email = this.cache.get(token)
if (!email) return false
this.cache.put(token, email, CONFIG.CACHE_EXPIRED_IN_SECONDS)
return email
}
getUserByEmail(email) {
email = email.trim().toLowerCase()
const ws = this.db.getSheetByName(CONFIG.SHEET_NAME.USERS)
if (!ws) throw new Error(`${CONFIG.SHEET_NAME.USERS} was not found in the database.`)
const [keys, ...records] = ws.getDataRange().getValues()
const indexOfEmail = keys.indexOf("User ID")
if (indexOfEmail === -1) throw new Error(`Header 'User ID' was not found in the table.`)
const record = records.find(v => v[indexOfEmail].toString().trim().toLocaleLowerCase() == email)
if (!record) return
return this.createItemObject(keys, record)
}
login({ email, password, token }) {
if (token) {
const isValidToken = this.validateToken(token)
if (!isValidToken) throw new Error(`Token tidak valid, silahkan login ulang.`)
return {
user: this.getUserByEmail(isValidToken),
token,
}
}
const user = this.getUserByEmail(email)
if (!user) throw new Error(`${email} User ID Salah.`)
if (password !== user.password) throw new Error(`User ID and Password Salah!`)
token = this.createToken(user)
user.token = token
return {
user,
token,
}
}
logout({ token }) {
this.cache.remove(token)
return {
success: true,
message: "Anda berhasil keluar!"
}
}
}
const app = new App()
const login = (params) => {
params = JSON.parse(params)
const data = app.login(params)
return JSON.stringify(data)
}
const logout = (params) => {
params = JSON.parse(params)
const data = app.logout(params)
return JSON.stringify(data)
}
function doGet() {
return HtmlService.createTemplateFromFile(CONFIG.INDEX)
.evaluate()
.setTitle(CONFIG.NAME)
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
}
function include_(filename) {
return HtmlService.createTemplateFromFile(filename).evaluate().getContent()
}
const CONFIG = { INDEX: "index.html", NAME: "Dashboard", DB_ID: "1BfS6GCnVKcaXiYHcMHm79q0vjQeShxp5H4K3Qtrwt8A", SHEET_NAME: { USERS: "users" }, CACHE_EXPIRED_IN_SECONDS: 21600, // 6 hours } class App { constructor() { this.db = SpreadsheetApp.openById(CONFIG.DB_ID) this.cache = CacheService.getScriptCache() } createToken(user) { const token = Utilities.getUuid() this.cache.put(token, user.email, CONFIG.CACHE_EXPIRED_IN_SECONDS) return token } createItemObject(keys, values) { const item = {} keys.forEach((key, index) => item[key] = values[index]) return item } validateToken(token) { const email = this.cache.get(token) if (!email) return false this.cache.put(token, email, CONFIG.CACHE_EXPIRED_IN_SECONDS) return email } getUserByEmail(email) { email = email.trim().toLowerCase() const ws = this.db.getSheetByName(CONFIG.SHEET_NAME.USERS) if (!ws) throw new Error(`${CONFIG.SHEET_NAME.USERS} was not found in the database.`) const [keys, ...records] = ws.getDataRange().getValues() const indexOfEmail = keys.indexOf("User ID") if (indexOfEmail === -1) throw new Error(`Header 'User ID' was not found in the table.`) const record = records.find(v => v[indexOfEmail].toString().trim().toLocaleLowerCase() == email) if (!record) return return this.createItemObject(keys, record) } login({ email, password, token }) { if (token) { const isValidToken = this.validateToken(token) if (!isValidToken) throw new Error(`Token tidak valid, silahkan login ulang.`) return { user: this.getUserByEmail(isValidToken), token, } } const user = this.getUserByEmail(email) if (!user) throw new Error(`${email} User ID Salah.`) if (password !== user.password) throw new Error(`User ID and Password Salah!`) token = this.createToken(user) user.token = token return { user, token, } } logout({ token }) { this.cache.remove(token) return { success: true, message: "Anda berhasil keluar!" } } } const app = new App() const login = (params) => { params = JSON.parse(params) const data = app.login(params) return JSON.stringify(data) } const logout = (params) => { params = JSON.parse(params) const data = app.logout(params) return JSON.stringify(data) } function doGet() { return HtmlService.createTemplateFromFile(CONFIG.INDEX) .evaluate() .setTitle(CONFIG.NAME) .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL) } function include_(filename) { return HtmlService.createTemplateFromFile(filename).evaluate().getContent() }
Penjelasan!
Ganti code DB_ID diatas dengan code Spreadsheets (Perhatikan bagian gambar yang di blok dibawah)
index.html
Copy script dibawah ini pada index.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<base target="_top">
</head>
<body>
<div id="app">
<v-app>
<my-snackbar></my-snackbar>
<my-navbar></my-navbar>
<v-main>
<v-container fluid>
<router-view></router-view>
</v-container>
</v-main>
<v-footer app>
</v-footer>
</v-app>
</div>
<?!= include_("vue/index.html"); ?>
</body>
</html>
<!DOCTYPE html> <html> <head> <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet"> <base target="_top"> </head> <body> <div id="app"> <v-app> <my-snackbar></my-snackbar> <my-navbar></my-navbar> <v-main> <v-container fluid> <router-view></router-view> </v-container> </v-main> <v-footer app> </v-footer> </v-app> </div> <?!= include_("vue/index.html"); ?> </body> </html>
vue/index.html
Copy script dibawah ini pada vue/index.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<?!= include_("utils.html"); ?>
<?!= include_("vue/vuetify.html"); ?>
<?!= include_("vue/store.html"); ?>
<?!= include_("vue/components.html"); ?>
<?!= include_("vue/router.html"); ?>
<script>
const app = new Vue({
el: "#app",
vuetify,
store,
router,
computed: {
...Vuex.mapState({
loading: "loading",
title: "title",
})
},
})
</script>
<style>.footer,.generic-footer{margin-bottom:98px}@media (min-width:52px){.footer,.generic-footer{margin-bottom:78px}}@media (min-width:52px){.footer,.generic-footer{margin-bottom:56px}}@media (min-width:52px){.footer,.generic-footer{margin-bottom:0}}.disclaimer{position:fixed;z-index:9999999;bottom:0;right:0;border-top:2px solid #ff5c62;text-align:center;font-size:14px;font-weight:400;background-color:#fff;padding:5px 10px 5px 10px}.disclaimer a:hover{text-decoration:underline}@media (min-width:52px){.disclaimer{text-align:right;border-left:2px solid red;border-top-left-radius:10px}}@media (min-width:1920px){.disclaimer{width:20%}}</style><div class="disclaimer">Version.01.05.22 @Copyright <a title="https://www.javabitpro.com/" target="_blank" href="https://www.javabitpro.com/" style="color: black;"><b>www.javabitpro.com</b></a></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> <?!= include_("utils.html"); ?> <?!= include_("vue/vuetify.html"); ?> <?!= include_("vue/store.html"); ?> <?!= include_("vue/components.html"); ?> <?!= include_("vue/router.html"); ?> <script> const app = new Vue({ el: "#app", vuetify, store, router, computed: { ...Vuex.mapState({ loading: "loading", title: "title", }) }, }) </script> <style>.footer,.generic-footer{margin-bottom:98px}@media (min-width:52px){.footer,.generic-footer{margin-bottom:78px}}@media (min-width:52px){.footer,.generic-footer{margin-bottom:56px}}@media (min-width:52px){.footer,.generic-footer{margin-bottom:0}}.disclaimer{position:fixed;z-index:9999999;bottom:0;right:0;border-top:2px solid #ff5c62;text-align:center;font-size:14px;font-weight:400;background-color:#fff;padding:5px 10px 5px 10px}.disclaimer a:hover{text-decoration:underline}@media (min-width:52px){.disclaimer{text-align:right;border-left:2px solid red;border-top-left-radius:10px}}@media (min-width:1920px){.disclaimer{width:20%}}</style><div class="disclaimer">Version.01.05.22 @Copyright <a title="https://www.javabitpro.com/" target="_blank" href="https://www.javabitpro.com/" style="color: black;"><b>www.javabitpro.com</b></a></div>
vue/vuetify.html
Copy script dibawah ini pada vue/vuetify.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script>
const lgith = {
primary: '#1976D2',
secondary: '#424242',
accent: '#82B1FF',
error: '#FF5252',
info: '#2196F3',
success: '#4CAF50',
warning: '#FFC107',
}
const dark = {
primary: '#1976D2',
secondary: '#424242',
accent: '#82B1FF',
error: '#FF5252',
info: '#2196F3',
success: '#4CAF50',
warning: '#FFC107',
}
const vuetify = new Vuetify({
theme: {
dark: false,
themes: {
lgith,
dark,
}
}
})
</script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script> <script> const lgith = { primary: '#1976D2', secondary: '#424242', accent: '#82B1FF', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FFC107', } const dark = { primary: '#1976D2', secondary: '#424242', accent: '#82B1FF', error: '#FF5252', info: '#2196F3', success: '#4CAF50', warning: '#FFC107', } const vuetify = new Vuetify({ theme: { dark: false, themes: { lgith, dark, } } }) </script>
vue/store.html
Copy script dibawah ini pada vue/store.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<script src="https://unpkg.com/vuex@3"></script>
<script>
const store = new Vuex.Store({
state: {
loading: false,
snackbar: {
message: null,
color: "primary",
timeout: 3000,
show: false,
},
name: "SiTE",
title: "Online Portal",
user: null,
},
getters: {
isAuth: (state) => {
return state.user ? true : false
}
},
mutations: {
updateState(state, {key, value}){
state[key] = value
},
startLoading(state){
state.loading = true
},
stopLoading(state){
state.loading = false
},
showSnackbar(state, snackbar){
snackbar.show = true
state.snackbar = snackbar
state.loading = false
},
closeSnackbar(state){
state.snackbar.show = false
},
},
actions: {
async login({commit}, params){
commit("startLoading")
try{
const { user, token} = await Utils.request("login", params)
console.log({user})
Utils.setToken(token)
commit("updateState", {key: "user", value: user})
commit("showSnackbar", {message: `Selamat Datang, ${user.name}!`, color: "success"})
router.push("/")
}catch(err){
commit("showSnackbar", {message: err.message, color: "error"})
}
},
async logout({commit}, params){
commit("startLoading")
try{
const {success, message} = await Utils.request("logout", params)
Utils.setToken("")
commit("updateState", {key: "user", value: null})
commit("showSnackbar", {message, color: "success"})
router.push("/login")
}catch(err){
commit("showSnackbar", {message: err.message, color: "error"})
}
}
},
})
</script>
<script src="https://unpkg.com/vuex@3"></script> <script> const store = new Vuex.Store({ state: { loading: false, snackbar: { message: null, color: "primary", timeout: 3000, show: false, }, name: "SiTE", title: "Online Portal", user: null, }, getters: { isAuth: (state) => { return state.user ? true : false } }, mutations: { updateState(state, {key, value}){ state[key] = value }, startLoading(state){ state.loading = true }, stopLoading(state){ state.loading = false }, showSnackbar(state, snackbar){ snackbar.show = true state.snackbar = snackbar state.loading = false }, closeSnackbar(state){ state.snackbar.show = false }, }, actions: { async login({commit}, params){ commit("startLoading") try{ const { user, token} = await Utils.request("login", params) console.log({user}) Utils.setToken(token) commit("updateState", {key: "user", value: user}) commit("showSnackbar", {message: `Selamat Datang, ${user.name}!`, color: "success"}) router.push("/") }catch(err){ commit("showSnackbar", {message: err.message, color: "error"}) } }, async logout({commit}, params){ commit("startLoading") try{ const {success, message} = await Utils.request("logout", params) Utils.setToken("") commit("updateState", {key: "user", value: null}) commit("showSnackbar", {message, color: "success"}) router.push("/login") }catch(err){ commit("showSnackbar", {message: err.message, color: "error"}) } } }, }) </script>
vue/router.html
Copy script dibawah ini pada vue/router.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<script src="https://unpkg.com/vue-router@3"></script>
<script>
const HomeView = {
template: `
<div>
<h1>Home</h1>
Welcome Dashboard V.01.05.22
<center><iframe src="?embedded=true"</iframe></center>
<iframe width="560" height="315" src="https://www.youtube.com/embed/KmgCM8HZD88" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<center><iframe src="https://script.google.com/macros/s/?embedded=true" width="100%" height="300" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe></center>
<center><iframe src="?embedded=true"</iframe></center>
</div>
`
}
const GoogleformView = {
template: `
<div>
<h1>Google Form View</h1>
<center><iframe src="?embedded=true"</iframe></center>
<center><iframe src="https://docs.google.com/forms/d/e/1FAIpQLSfCwPNLaWWR_YhyyK15OqKKEeM0pnLHwvelXIHtZvDak2R8Jg/viewform?embedded=true" width="100%" height="1500" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe></center>
<center><iframe src="?embedded=true"</iframe></center>
</div>
`
}
const SpreadsheetView = {
template: `
<div>
<h1>Spreadsheet View</h1>
<center><iframe src="?embedded=true"</iframe></center>
<center><iframe src="https://docs.google.com/spreadsheets/d/1wqncKhk6ufgZ-UVryrmP9bLO1teFavxtAq9XPquRbPM/edit?usp=sharing?embedded=true" width="100%" height="800" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe></center>
<center><iframe src="?embedded=true"</iframe></center>
</div>
`
}
const LoginView = {
data: function(){
return {
form: {
email: {
value: null,
label: "User ID",
icon: "mdi-account-box",
type: "email",
rules: [v => !!v || "User ID harus diisi"],
},
password: {
value: null,
label: "Password",
icon: "mdi-lock",
type: "password",
rules: [v => !!v || "Password harus diisi"],
},
},
}
},
methods: {
login(){
const isFormValid = this.$refs.form.validate()
if (!isFormValid) return
const email = this.form.email.value
const password = this.form.password.value
this.$store.dispatch("login", {email, password})
},
},
computed: {
...Vuex.mapState({
loading: "loading",
}),
},
template: `
<div>
<v-progress-linear
:active="loading"
:indeterminate="loading"
absolute
top
color="primary"
></v-progress-linear>
<v-row>
<v-spacer></v-spacer>
<v-col cols="12" sm="8" md="6" lg="4">
<h1 class="mb-4 primary--text">Login</h1>
<v-form ref="form" @submit.prevent="login">
<my-input :item="form.email"></my-input>
<my-input :item="form.password"></my-input>
<v-btn type="submit" color="primary" depressed block :disabled="loading">
<v-icon left>mdi-login</v-icon>
Login
</v-btn>
</v-form>
</v-col>
<v-spacer></v-spacer>
</v-row>
</div>
`
}
const routes = [
{ path: '/', component: HomeView, meta: {icon: "mdi-home", title: "Home"} },
{ path: '/Googleform', component: GoogleformView, meta: {icon: "mdi-file-document", title: "Input"} },
{ path: '/Spreadsheet', component: SpreadsheetView, meta: {icon: "mdi-file-excel", title: "Spreadsheet"} },
{ path: '/login', component: LoginView, meta: {icon: "mdi-login", title: "Login"} },
]
const router = new VueRouter({
routes
})
router.beforeEach( async (to, from, next) => {
const token = Utils.getToken()
let isAuth = store.getters.isAuth
if (token && !isAuth) {
await store.dispatch("login", {token})
isAuth = store.getters.isAuth
}
if (to.path !== '/login' && !isAuth) return next("/login")
return next()
})
router.afterEach((to, from) => {
google.script.history.push(null, null, to.path)
})
</script>
<script src="https://unpkg.com/vue-router@3"></script> <script> const HomeView = { template: ` <div> <h1>Home</h1> Welcome Dashboard V.01.05.22 <center><iframe src="?embedded=true"</iframe></center> <iframe width="560" height="315" src="https://www.youtube.com/embed/KmgCM8HZD88" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <center><iframe src="https://script.google.com/macros/s/?embedded=true" width="100%" height="300" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe></center> <center><iframe src="?embedded=true"</iframe></center> </div> ` } const GoogleformView = { template: ` <div> <h1>Google Form View</h1> <center><iframe src="?embedded=true"</iframe></center> <center><iframe src="https://docs.google.com/forms/d/e/1FAIpQLSfCwPNLaWWR_YhyyK15OqKKEeM0pnLHwvelXIHtZvDak2R8Jg/viewform?embedded=true" width="100%" height="1500" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe></center> <center><iframe src="?embedded=true"</iframe></center> </div> ` } const SpreadsheetView = { template: ` <div> <h1>Spreadsheet View</h1> <center><iframe src="?embedded=true"</iframe></center> <center><iframe src="https://docs.google.com/spreadsheets/d/1wqncKhk6ufgZ-UVryrmP9bLO1teFavxtAq9XPquRbPM/edit?usp=sharing?embedded=true" width="100%" height="800" frameborder="0" marginheight="0" marginwidth="0">Loading…</iframe></center> <center><iframe src="?embedded=true"</iframe></center> </div> ` } const LoginView = { data: function(){ return { form: { email: { value: null, label: "User ID", icon: "mdi-account-box", type: "email", rules: [v => !!v || "User ID harus diisi"], }, password: { value: null, label: "Password", icon: "mdi-lock", type: "password", rules: [v => !!v || "Password harus diisi"], }, }, } }, methods: { login(){ const isFormValid = this.$refs.form.validate() if (!isFormValid) return const email = this.form.email.value const password = this.form.password.value this.$store.dispatch("login", {email, password}) }, }, computed: { ...Vuex.mapState({ loading: "loading", }), }, template: ` <div> <v-progress-linear :active="loading" :indeterminate="loading" absolute top color="primary" ></v-progress-linear> <v-row> <v-spacer></v-spacer> <v-col cols="12" sm="8" md="6" lg="4"> <h1 class="mb-4 primary--text">Login</h1> <v-form ref="form" @submit.prevent="login"> <my-input :item="form.email"></my-input> <my-input :item="form.password"></my-input> <v-btn type="submit" color="primary" depressed block :disabled="loading"> <v-icon left>mdi-login</v-icon> Login </v-btn> </v-form> </v-col> <v-spacer></v-spacer> </v-row> </div> ` } const routes = [ { path: '/', component: HomeView, meta: {icon: "mdi-home", title: "Home"} }, { path: '/Googleform', component: GoogleformView, meta: {icon: "mdi-file-document", title: "Input"} }, { path: '/Spreadsheet', component: SpreadsheetView, meta: {icon: "mdi-file-excel", title: "Spreadsheet"} }, { path: '/login', component: LoginView, meta: {icon: "mdi-login", title: "Login"} }, ] const router = new VueRouter({ routes }) router.beforeEach( async (to, from, next) => { const token = Utils.getToken() let isAuth = store.getters.isAuth if (token && !isAuth) { await store.dispatch("login", {token}) isAuth = store.getters.isAuth } if (to.path !== '/login' && !isAuth) return next("/login") return next() }) router.afterEach((to, from) => { google.script.history.push(null, null, to.path) }) </script>
Penjelasan!
WARNA MERAH : iframe src="url?embedded=true"
utils.html
5. Klik tombol Deploy / Terapkan - Deployment baru
Silahkan ubah url yang bergaris bawah dengan link / file yang akan ditampilkan di webiste
Gambar 1.
WARNA HIJAU : Ejaan teks harus sama (perhatikan gambar 1 dan gambar 2)
WARNA BIRU : Apabila ada penambahan menu, copy script di area biru dan di paste di bawahnya.
WARNA KUNING : Apabila ada penambahan menu, setelah langkah WARNA BIRU selesai, maka pada garis kuning dibawah silahkan di isi script seperti diatasnya
Gambar 2.
vue/components.html
Copy script dibawah ini pada vue/components.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<script>
const MySnackbar = Vue.component("my-snackbar", {
computed: {
...Vuex.mapState({
snackbar: "snackbar",
}),
},
template: `
<v-snackbar
v-model="snackbar.show"
:timeout="snackbar.timeout || 3000"
:color="snackbar.color || 'dark'"
>
{{ snackbar.message }}
<template v-slot:action="{ attrs }">
<v-btn
color="white"
text
v-bind="attrs"
@click="$store.commit('closeSnackbar')"
>
Close
</v-btn>
</template>
</v-snackbar>
`
})
const MyInput = Vue.component("my-input", {
props: {
item: Object,
},
computed: {
...Vuex.mapState({
loading: "loading",
}),
},
template: `
<v-text-field
:outlined="item.outlined || true"
:dense="item.dense || true"
:label="item.label"
:prepend-inner-icon="item.icon || null"
:placeholder="item.placeholder"
:type="item.type"
:hideDetails="item.hideDetails || false"
:rules="item.rules"
:disabled="item.loading || loading"
v-model="item.value"
@change="$emit('change')"
></v-text-field>
`
})
const MyNavbar = Vue.component('my-navbar', {
data: function(){
return {
drawer: true,
}
},
computed: {
pages(){
console.log(this.$router)
return this.$router.options.routes
},
...Vuex.mapState({
name: "name",
title: "title",
loading: "loading",
user: "user",
}),
...Vuex.mapGetters({
isAuth: "isAuth",
}),
},
methods: {
logout(){
const token = this.user.toekn // get the token from local storage
this.$store.dispatch("logout", {token})
},
},
template: `
<div v-if="isAuth">
<v-navigation-drawer app v-model="drawer">
<v-list-item>
<v-list-item-content>
<v-list-item-title class="text-h6">
{{ user.name }}
</v-list-item-title>
<v-list-item-subtitle>
{{ title }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list
dense
nav
>
<template v-for="item in pages">
<v-list-item
v-if="item.path !== '/login'"
:key="item.path"
:to="item.path"
link
>
<v-list-item-icon>
<v-icon>{{ item.meta.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.meta.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</template>
<v-list-item link @click="$vuetify.theme.dark = !$vuetify.theme.dark">
<v-list-item-icon>
<v-icon>{{ $vuetify.theme.dark ? 'mdi-lightbulb-on' : 'mdi-lightbulb-off' }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ $vuetify.theme.dark ? 'Light Mode' : 'Dark Mode' }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link @click="logout">
<v-list-item-icon>
<v-icon>mdi-logout</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Logout</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar app flat>
<v-progress-linear
:active="loading"
:indeterminate="loading"
absolute
bottom
color="primary"
></v-progress-linear>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>{{ $route.meta.title}}</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
</div>
`
})
</script>
<script> const MySnackbar = Vue.component("my-snackbar", { computed: { ...Vuex.mapState({ snackbar: "snackbar", }), }, template: ` <v-snackbar v-model="snackbar.show" :timeout="snackbar.timeout || 3000" :color="snackbar.color || 'dark'" > {{ snackbar.message }} <template v-slot:action="{ attrs }"> <v-btn color="white" text v-bind="attrs" @click="$store.commit('closeSnackbar')" > Close </v-btn> </template> </v-snackbar> ` }) const MyInput = Vue.component("my-input", { props: { item: Object, }, computed: { ...Vuex.mapState({ loading: "loading", }), }, template: ` <v-text-field :outlined="item.outlined || true" :dense="item.dense || true" :label="item.label" :prepend-inner-icon="item.icon || null" :placeholder="item.placeholder" :type="item.type" :hideDetails="item.hideDetails || false" :rules="item.rules" :disabled="item.loading || loading" v-model="item.value" @change="$emit('change')" ></v-text-field> ` }) const MyNavbar = Vue.component('my-navbar', { data: function(){ return { drawer: true, } }, computed: { pages(){ console.log(this.$router) return this.$router.options.routes }, ...Vuex.mapState({ name: "name", title: "title", loading: "loading", user: "user", }), ...Vuex.mapGetters({ isAuth: "isAuth", }), }, methods: { logout(){ const token = this.user.toekn // get the token from local storage this.$store.dispatch("logout", {token}) }, }, template: ` <div v-if="isAuth"> <v-navigation-drawer app v-model="drawer"> <v-list-item> <v-list-item-content> <v-list-item-title class="text-h6"> {{ user.name }} </v-list-item-title> <v-list-item-subtitle> {{ title }} </v-list-item-subtitle> </v-list-item-content> </v-list-item> <v-list dense nav > <template v-for="item in pages"> <v-list-item v-if="item.path !== '/login'" :key="item.path" :to="item.path" link > <v-list-item-icon> <v-icon>{{ item.meta.icon }}</v-icon> </v-list-item-icon> <v-list-item-content> <v-list-item-title>{{ item.meta.title }}</v-list-item-title> </v-list-item-content> </v-list-item> </template> <v-list-item link @click="$vuetify.theme.dark = !$vuetify.theme.dark"> <v-list-item-icon> <v-icon>{{ $vuetify.theme.dark ? 'mdi-lightbulb-on' : 'mdi-lightbulb-off' }}</v-icon> </v-list-item-icon> <v-list-item-content> <v-list-item-title>{{ $vuetify.theme.dark ? 'Light Mode' : 'Dark Mode' }}</v-list-item-title> </v-list-item-content> </v-list-item> <v-list-item link @click="logout"> <v-list-item-icon> <v-icon>mdi-logout</v-icon> </v-list-item-icon> <v-list-item-content> <v-list-item-title>Logout</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> </v-navigation-drawer> <v-app-bar app flat> <v-progress-linear :active="loading" :indeterminate="loading" absolute bottom color="primary" ></v-progress-linear> <v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon> <v-toolbar-title>{{ $route.meta.title}}</v-toolbar-title> <v-spacer></v-spacer> </v-app-bar> </div> ` }) </script>
utils.html
Copy script dibawah ini pada utils.html
Masukkan Password Untuk Melihat Script (Password sama dengan di atas)
<script>
const Utils = {
KEY: {
TOKEN: "_token"
},
}
Utils.request = async (functionName, params={}) => {
params = JSON.stringify(params)
return new Promise((resolve, reject) => {
google.script.run
.withSuccessHandler((response) => resolve(JSON.parse(response)))
.withFailureHandler((err) => reject(err))
[functionName](params)
})
}
Utils.setToken = (token) => {
if (typeof token === "object") token = JSON.stringify(token)
try{
localStorage.setItem(Utils.KEY.TOKEN, token)
}catch(err){
// pass
console.log(err)
}
}
Utils.getToken = () => {
try{
return localStorage.getItem(Utils.KEY.TOKEN)
}catch(err){
// pass
console.log(err)
}
}
</script>
<script> const Utils = { KEY: { TOKEN: "_token" }, } Utils.request = async (functionName, params={}) => { params = JSON.stringify(params) return new Promise((resolve, reject) => { google.script.run .withSuccessHandler((response) => resolve(JSON.parse(response))) .withFailureHandler((err) => reject(err)) [functionName](params) }) } Utils.setToken = (token) => { if (typeof token === "object") token = JSON.stringify(token) try{ localStorage.setItem(Utils.KEY.TOKEN, token) }catch(err){ // pass console.log(err) } } Utils.getToken = () => { try{ return localStorage.getItem(Utils.KEY.TOKEN) }catch(err){ // pass console.log(err) } } </script>
5. Klik tombol Deploy / Terapkan - Deployment baru