TEMPLATE VUETIFY, VUE 2 DAN VUE-ROUTER versi 2
Berikut ini adalah skrip template vue dengan kecepatan dan keandalan yang luar biasa, karna pemanggilan dan penggunaan skrip sangatlah mudah.
<!DOCTYPE html><html lang="en" style="overflow: hidden;">
<head> <meta charset="utf-8"> <title>Vue Vuetify Dialog Dynamic</title> <link href="https://cdn.jsdelivr.net/npm/@mdi/font/css/materialdesignicons.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/vuetify@2.5.10/dist/vuetify.min.css" rel="stylesheet"></head>
<body> <div id="app"> <v-app> <!-- Global Snackbar --> <v-snackbar v-model="snackbar.show" :timeout="snackbar.timeout" :color="snackbar.color"> {{ snackbar.text }} <template v-slot:action="{ attrs }"> <v-btn text v-bind="attrs" @click="snackbar.show = false">Tutup</v-btn> </template> </v-snackbar>
<!-- Dialog Stack --> <template v-for="(dialog, index) in dialogs"> <v-dialog :key="dialog.id" v-model="dialog.show" :max-width="dialog.width" :persistent="dialog.persistent" :z-index="500 + index * 10" > <v-card v-if="dialog.type !== 'loading'"> <v-toolbar dark class="mb-4" color="primary"> <v-toolbar-title>{{ dialog.title }}</v-toolbar-title> </v-toolbar> <v-card-text> <div v-if="dialog.type === 'info'">{{ dialog.content }}</div> <component v-else :is="dialog.template" v-model="dialog.data"></component> </v-card-text> <v-card-actions> <v-spacer></v-spacer> <v-btn color="primary" @click="() => { dialog.actions.ok.action(dialog.data); if (dialog.actions.ok.close !== false) { closeDialog(dialog.id); } }" > {{ dialog.actions.ok.title || 'OK' }} </v-btn> <v-btn v-if="dialog.actions.cancel" text @click="() => { if (dialog.actions.cancel.action) { dialog.actions.cancel.action(dialog.data); } closeDialog(dialog.id); }" > {{ dialog.actions.cancel.title || 'Cancel' }} </v-btn> </v-card-actions> </v-card> <v-card v-else flat tile> <v-card-text> <div style="display: flex; justify-content: center; align-items: center; height: 100px;"> <v-progress-circular indeterminate color="primary" size="40" /> </div> </v-card-text> </v-card> </v-dialog> </template>
<router-view /> </v-app> </div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue-router@3.6.5/dist/vue-router.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vuetify@2.5.10/dist/vuetify.js"></script>
<script> const crud = (op, sheetName, params) => { const db = JSON.parse(localStorage.getItem('localDB')) || {}; if (!db[sheetName]) db[sheetName] = { headers: [], rows: [] }; const sheet = db[sheetName]; const res = { status: '', data: [], message: '' };
if (op === 'create') { JSON.parse(params).forEach(d => { if (!sheet.headers.length) sheet.headers = Object.keys(d); sheet.rows.push(sheet.headers.map(h => d[h] || '')); }); localStorage.setItem('localDB', JSON.stringify(db)); res.status = 'success'; } else if (op === 'read') { res.data = sheet.rows.map(row => sheet.headers.reduce((o, h, i) => (o[h] = row[i], o), {})); res.status = 'success'; } return JSON.stringify(res); };
const Dashboard = { template: `<v-container> <h2>Dashboard</h2> <p>Selamat datang di dashboard.</p> </v-container>` };
const ListView = { data: () => ({ items: [], username: '' }), created() { const user = localStorage.getItem('aktif'); this.username = user; const key = `items-${user}`; if (!JSON.parse(crud("read", key)).data.length) crud("create", key, JSON.stringify([{ name: "Item A", email: "" }])); this.items = JSON.parse(crud("read", key)).data; }, methods: { async editItem(item) { const editDialogId = this.$root.showDialog({ type: 'form', title: 'Edit Data', content: ` <v-text-field label="Nama" v-model="value.name"></v-text-field> <v-text-field label="Email" v-model="value.email"></v-text-field> `, data: { name: item.name, email: item.email }, actions: { ok: { title: 'Simpan', close: false, action: async (data) => { const confirm = await new Promise(resolve => { const confirmDialogId = this.$root.showDialog({ type: 'info', title:'Simpan', content: 'Yakin ingin menyimpan perubahan?',width: '300px', actions: { ok: { title: 'Ya', action: () => { this.$root.closeDialog(confirmDialogId); resolve(true); } }, cancel: { title: 'Tidak', action: () => { this.$root.closeDialog(confirmDialogId); resolve(false); } } } }); });
if (confirm) { const loadingDialogId = this.$root.showDialog({ type: 'loading', content: '', width: '100px', persistent: true, actions: { ok: { show: false, action: () => {} } } });
try { await new Promise(resolve => setTimeout(resolve, 1000)); const key = `items-${this.username}`; const all = JSON.parse(crud("read", key)).data; const index = all.findIndex(x => x.name === item.name); if (index !== -1) { all[index] = data; localStorage.setItem('localDB', JSON.stringify({ ...JSON.parse(localStorage.getItem('localDB')), [key]: { headers: ['name', 'email'], rows: all.map(d => [d.name, d.email]) } })); this.items = all; this.$root.showSnackbar("Data berhasil disimpan!", "success"); } } finally { this.$root.closeDialog(loadingDialogId); this.$root.closeDialog(editDialogId); } } } }, cancel: { title: 'Batal', action: () => { this.$root.showSnackbar("Edit dibatalkan", "info"); } } } }); } }, template: `<v-container> <v-simple-table dense> <thead><tr><th>Nama</th></tr></thead> <tbody> <tr v-for="(item, i) in items" :key="i" @click="editItem(item)"> <td>{{ item.name }}</td> </tr> </tbody> </v-simple-table> </v-container>` };
const Login = { data: () => ({ isRegister: false, user: { username: '', password: '' } }), methods: { toggleMode() { this.isRegister = !this.isRegister }, submit() { const { username, password } = this.user; if (!username || !password) { this.$root.showSnackbar('Username dan password harus diisi','warning'); return; }
const users = JSON.parse(crud("read", "users")).data; if (this.isRegister) { if (users.find(u => u.username === username)) { this.$root.showSnackbar('Username sudah terdaftar!','warning'); return; } crud("create", "users", JSON.stringify([this.user])); this.$root.showSnackbar('Registrasi berhasil', 'success'); this.toggleMode(); } else { const match = users.find(u => u.username === username && u.password === password); if (match) { localStorage.setItem('aktif', username); this.$root.showSnackbar('Login berhasil', 'success'); this.$router.push("/app/dashboard"); } else { this.$root.showSnackbar('Login Gagal, username/password salah!', 'error'); } } } }, template: `<v-container fill-height> <v-row align="center" justify="center"> <v-col cols="12" md="10"> <v-card class="mx-auto" :style="{ maxWidth: '290px' }"> <v-toolbar dark color="primary"> <v-toolbar-title>{{ isRegister ? 'Register' : 'Login' }}</v-toolbar-title> </v-toolbar> <v-card-text> <v-text-field v-model="user.username" label="Username" /> <v-text-field v-model="user.password" label="Password" type="password" /> </v-card-text> <v-card-actions> <v-btn color="primary" @click="submit">{{ isRegister ? 'Register' : 'Login' }}</v-btn> <v-spacer></v-spacer> <v-btn text @click="toggleMode"> {{ isRegister ? 'Sudah punya akun?' : 'Belum punya akun?' }} </v-btn> </v-card-actions> </v-card> </v-col> </v-row> </v-container>` };
const appChildRoutes = [ { path: 'dashboard', title: 'Dashboard', icon: 'mdi-view-dashboard', component: Dashboard }, { path: 'list', title: 'List View', icon: 'mdi-format-list-bulleted', component: ListView } ];
const AppLayout = { data: () => ({ drawer: false, links: appChildRoutes }), computed: { currentTitle() { return (this.links.find(r => '/app/' + r.path === this.$route.path) || {}).title || "My App"; } }, methods: { go(path) { const full = '/app/' + path; if (this.$route.path !== full) this.$router.push(full); this.drawer = false; }, logout() { localStorage.removeItem('aktif'); this.$root.showSnackbar("Berhasil logout", "info"); this.$router.push('/'); } },
template: `<v-app> <v-navigation-drawer app v-model="drawer"> <v-list dense> <v-list-item v-for="r in links" :key="r.path" link @click="go(r.path)"> <v-list-item-icon><v-icon>{{ r.icon }}</v-icon></v-list-item-icon> <v-list-item-title>{{ r.title }}</v-list-item-title> </v-list-item> <v-divider></v-divider> <v-list-item link @click="logout"> <v-list-item-icon><v-icon>mdi-logout</v-icon></v-list-item-icon> <v-list-item-title>Logout</v-list-item-title> </v-list-item> </v-list>
</v-navigation-drawer> <v-app-bar app color="primary" dark> <v-app-bar-nav-icon @click="drawer = !drawer" /> <v-toolbar-title>{{ currentTitle }}</v-toolbar-title> </v-app-bar> <v-main><router-view /></v-main> </v-app>` };
const router = new VueRouter({ routes: [ { path: '/', component: Login }, { path: '/app', component: AppLayout, children: appChildRoutes } ] });
router.beforeEach((to, from, next) => { const loggedIn = localStorage.getItem('aktif'); if (to.path.startsWith('/app') && !loggedIn) { next('/'); } else { next(); } });
new Vue({ el: '#app', vuetify: new Vuetify(), router, data: () => ({ dialogs: [], snackbar: { show: false, text: '', color: 'success', timeout: 3000 }, defaultDialog: { type: '', title: '', template: null, content: null, data: null, width: '500px', persistent: true, actions: { ok: { title: 'OK', action: () => {} }, cancel: null } } }), created() { localStorage.removeItem('aktif'); }, methods: { showDialog(typeOrOptions, title = '', content = '') { let options = {}; if (typeof typeOrOptions === 'string') { options = { type: typeOrOptions, title, content }; } else { options = typeOrOptions; }
const dialog = { ...this.defaultDialog, ...options, id: 'dialog-' + Date.now() + Math.random().toString(36).substr(2, 5), show: true };
if (options.type === 'form') { const componentId = 'dynamic-form-' + dialog.id; Vue.component(componentId, { props: ['value'], template: `<div>${options.content}</div>` }); dialog.template = componentId; } else if (options.type === 'loading') { const componentId = 'loading-dialog-' + dialog.id; Vue.component(componentId, { template: ` <div style="display: flex; justify-content: center; align-items: center; height: 100px;"> <v-progress-circular indeterminate color="primary" size="40" /> </div> ` }); dialog.template = componentId; }
this.dialogs.push(dialog); return dialog.id; }, closeDialog(id) { this.dialogs = this.dialogs.filter(dialog => dialog.id !== id); }, showSnackbar(text, color = 'success') { this.snackbar.text = text; this.snackbar.color = color; this.snackbar.show = true; } } }); </script></body>
</html>