TEMPLATE VUETIFY, VUE 2 DAN VUE-ROUTER
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">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue App</title>
<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.4.95/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.6.15/dist/vuetify.min.css" rel="stylesheet">
</head>
<body>
<div id="app">
<v-app>
<v-dialog persistent v-for="dialog in dialogs" :key="dialog.id" v-model="dialog.visible" :max-width="dialog.lebar">
<template v-if="dialog.id === 'loading'">
<v-card>
<v-card-title class="layout justify-center">{{dialog.title}}</v-card-title>
<v-card-text>
<v-container class="d-flex justify-center align-center" style="height: 40px;">
<v-progress-circular indeterminate color="primary"></v-progress-circular>
</v-container>
</v-card-text>
</v-card>
</template>
<template v-else>
<v-card>
<v-card-title>{{dialog.title}}</v-card-title>
<v-card-text>
<component :is="dialog.template" :data="dialog.data"></component>
</v-card-text>
<v-card-actions>
<v-btn text v-if="dialog.extra" :color="dialog.extra.color || 'secondary'" @click="dialog.extra.action(dialog)">{{ dialog.extra.title }}</v-btn>
<v-spacer></v-spacer>
<v-btn text v-if="dialog.ok" :color="dialog.ok.color || 'primary'" @click="dialog.ok.action(dialog)">{{ dialog.ok.title }}</v-btn>
<v-btn text @click="closeDialog(dialog.id)">Batal</v-btn>
</v-card-actions>
</v-card>
</template>
</v-dialog>
<v-snackbar v-model="pesan.show" timeout="2000">
{{ pesan.title }}
<template v-slot:action="{ attrs }">
<v-btn color="blue" text v-bind="attrs" @click="pesan.show = false">
Close</v-btn>
</template>
</v-snackbar>
<router-view></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.6.15"></script>
<template id="listusers">
<v-card>
<v-card-text>
<v-list>
<v-list-item v-for="(user, index) in listuser" :key="index">
<v-list-item-content>
<v-list-item-title>{{ user.username }}</v-list-item-title>
<v-list-item-subtitle>{{ user.name }}</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<div>
<v-btn icon color="red" @click="deleteUser(index)">
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-btn icon color="blue" @click="bukaEditing(user, index)">
<v-icon>mdi-pencil</v-icon>
</v-btn>
</div>
</v-list-item-action>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</template>
<script>
const ListUser = {
template: "#listusers",
data: () => ({
listuser: [],
}),
methods: {
fetchUsers() {
this.listuser = StorageCRUD("user").tampilData();
},
bukaEditing(user = null, index = null) {
const isEdit = user !== null;
app.openDialog(
"form",
isEdit ? "Edit User" : "Tambah User",
{
template: `
<v-form>
<v-text-field v-model="data.name" label="Nama"></v-text-field>
<v-text-field v-model="data.username" label="Username"></v-text-field>
<v-text-field v-model="data.password" label="Password" type="password"></v-text-field>
</v-form>
`,
props: ["data"],
},
isEdit ? { ...user } : { name: "", username: "", password: "" },
{
title: "Simpan",
action: (dialog) => {
let berhasil;
if (isEdit) {
berhasil = StorageCRUD("user").edit(user.id, dialog.data);
} else {
berhasil = StorageCRUD("user").simpan(dialog.data);
}
if (berhasil) {
app.tampilpesan(isEdit ? "User berhasil diperbarui" : "User berhasil ditambahkan");
this.fetchUsers();
} else {
app.tampilpesan(isEdit ? "Gagal memperbarui user" : "Gagal menambahkan user");
}
app.closeDialog("form");
},
},
);
},
deleteUser(index) {
const user = this.listuser[index];
app.openDialog(
"confirm",
"Konfirmasi",
"Yakin ingin menghapus user ini?",
null,
{
title: "Ya",
color: "red",
action: () => {
const berhasil = StorageCRUD("user").hapus(user.id);
if (berhasil) {
app.tampilpesan("User berhasil dihapus");
this.fetchUsers();
} else {
app.tampilpesan("Gagal menghapus user");
}
app.closeDialog("confirm");
},
},
);
},
},
mounted(){
this.fetchUsers();
app.actionButtons= [{ name: 'edit', icon: 'mdi-plus-box',
method:()=>{
this.bukaEditing();
}
}];
app.floatingButtons= {
fab:false,
icon: 'mdi-plus',
color: 'primary',
method: () => console.log('Fab1'),
children: [
{
icon: 'mdi-pencil',
color: 'secondary',
method: () => console.log('Child 1'),
}, {
icon: 'mdi-delete',
color: 'error',
method: () => console.log('Child 2'),
},
],
}
}
}
</script>
<template id="tesdialog">
<v-card>
<v-card-text>
</v-card-text>
<v-card-actions>
<v-btn @click="showFormDialog">Show Form Dialog</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
const TesDialog = {
template: '#tesdialog',
data: () => ({
}),
methods: {
showFormDialog() {
app.openDialog('form', 'input pelanggan',
{ props: ['data'],
template: `
<v-form>
<v-text-field v-model="data.nama" label="Nama"></v-text-field>
<v-text-field v-model="data.address" label="Alamat"></v-text-field>
</v-form> `,
},
{ nama: 'Ali', address: 'Brongkal' },
{ title: 'Simpan',
action: (dialog) => {
app.openDialog('confirm', 'Konfirmasi', 'Yakin akan menyimpan data?',null,
{ title: 'Iya', action: () => {
console.log(dialog.data);
app.closeDialog('form');
app.closeDialog('confirm');
}},
{ title: 'Tidak', action: () => {
app.openDialog('loading', 'Loading...', null, null, null, null);
setTimeout(() => {
app.closeDialog('loading');
console.log(dialog.data);
}, 3000);
}},
);
}
});
},
},
mounted(){
app.floatingButtons= {
fab:false,
icon: 'mdi-plus',
color: 'primary',
method: () => console.log('Fab1'),
}
}
};
</script>
<template id="autentifikasi">
<v-main>
<v-container fill-height>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<v-card class="mx-auto" :style="{ maxWidth: '290px' }">
<v-toolbar dark color="primary">
<v-toolbar-title>
{{title}}
</v-toolbar-title>
<v-spacer></v-spacer> <div v-for="action in $root.actionButtons" :key="action.name"> <v-btn icon @click="action.method"> <v-icon>{{ action.icon }}</v-icon> </v-btn> </div>
</v-toolbar>
<v-card-text>
<v-text-field v-if="register" v-model="name" label="Nama"></v-text-field>
<v-text-field v-model="username" label="Username"></v-text-field>
<v-text-field v-model="password" label="Password" type="password"></v-text-field>
</v-card-text>
<v-card-actions>
<v-btn @click="login" text color="blue" v-if="!register">Login</v-btn>
<v-btn @click="registerUser" text color="blue" v-if="register">Kirim</v-btn>
<v-btn @click="toggleRegister" text color="green">{{ register ? 'Kembali' : 'Registrasi'
}}</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</v-main>
</template>
<script>
const Autentifikasi = {
template: '#autentifikasi',
data: () => ({
title: 'Login',
username: 'ali',
password: 'ali',
name: '',
register: false
}),
methods: {
login() {
const ditemukan = StorageCRUD("user").cari({ username:this.username, password: this.password});
if(ditemukan.length>0){
app.tampilpesan('Login berhasil');
app.useraktif=ditemukan[0];
this.$router.push('/dashboard');
}else{
app.tampilpesan('Login gagal');
}
},
registerUser() {
const berhasil = StorageCRUD("user").simpan({ name: this.name, password: this.password, username:this.username });
if(berhasil){
app.tampilpesan('Pendaftaran berhasil');
this.toggleRegister();
}else{
app.tampilpesan('Pendaftaran gagal');
}
},
toggleRegister() {
this.register = !this.register;
this.title = this.register ? 'Registrasi' : 'Login';
}
}
}
</script>
<template id="applayout">
<div>
<v-app-bar app color="primary" dark>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer> <div v-for="action in $root.actionButtons" :key="action.name"> <v-btn icon @click="action.method"> <v-icon>{{ action.icon }}</v-icon> </v-btn> </div>
</v-app-bar>
<v-navigation-drawer v-model="drawer" app>
<v-list dense>
<v-list-item-group>
<v-list-item v-for="route in routes" :key="route.path"
@click="pindahHalaman(route.path,route.meta.title)">
<v-list-item-icon>
<v-icon>{{ route.meta.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-title>{{ route.meta.title }}</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-list>
</v-navigation-drawer>
<v-main style="height: 100vh;">
<v-layout fill-height overflow-auto>
<v-container fluid>
<router-view></router-view>
<div v-if="$root.floatingButtons && Object.keys($root.floatingButtons).length > 0">
<div v-if="$root.floatingButtons.children && $root.floatingButtons.children.length > 0" style="position: fixed; bottom: 16px; right: 16px;">
<v-speed-dial v-model="$root.floatingButtons.fab" direction="top" :open-on-hover="false" transition="scale-transition">
<template v-slot:activator>
<v-btn :color="$root.floatingButtons.color" fab dark>
<v-icon>{{ $root.floatingButtons.icon }}</v-icon>
</v-btn>
</template>
<v-btn v-for="(button, index) in $root.floatingButtons.children" :key="index" :color="button.color" fab dark @click="button.method">
<v-icon>{{ button.icon }}</v-icon>
</v-btn>
</v-speed-dial>
</div>
<div v-else style="position: fixed; bottom: 16px; right: 16px;">
<v-btn @click="$root.floatingButtons.method" :color="$root.floatingButtons.color" fab dark>
<v-icon>{{ $root.floatingButtons.icon }}</v-icon>
</v-btn>
</div>
</div>
</v-container>
</v-layout>
</v-main>
</div>
</template>
<script>
const appLayout = {
template: '#applayout',
data: () => ({
drawer: false,
title: 'Aplikasi',
routes: navigasiaplikasi
}),
methods: {
pindahHalaman(path, judul) {
if (this.$route.path !== path) {
this.title = judul;
this.$router.push(path);
}
}
},
created() {
this.title = this.$route.meta.title;
}
}
</script>
<script>
const Dashboard = {
data: () => ({ useraktif: app.useraktif }),
template: `<v-card><v-card-text><h1>Selamat datang,</h1></br><h2>{{ useraktif.name }}</h2></v-card-text></v-card>`
};
const Logout = {
template: '<div></div>',
created() {
app.useraktif = null;
this.$nextTick(() => {
this.$router.replace('/login');
});
}
};
const navigasiaplikasi = [
{
path: '/dashboard', component: Dashboard,
meta: { title: 'Dashboard', icon: 'mdi-view-dashboard' }
},
{
path: '/listuser', component: ListUser,
meta: { title: 'List User', icon: 'mdi-account' }
},
{
path: '/tesdialog', component: TesDialog,
meta: { title: 'Tes Dialog', icon: 'mdi-account' }
},
{
path: '/logout', component: Logout,
meta: { title: 'Logout', icon: 'mdi-logout' }
},
];
const routes = [
{
path: '/', redirect: '/login', component: appLayout,
children: navigasiaplikasi
},
{ path: '/login', component: Autentifikasi },
{ path: '*', redirect: '/' }
];
const router = new VueRouter({
routes
});
const app = new Vue({
el: '#app',
router,
vuetify: new Vuetify(),
data: () => ({
dialogs: [],
pesan: {
show: false,
title: ''
},
actionButtons:[],
floatingButtons:{},
useraktif:{},
}),
methods: {
tampilpesan(pesan) {
this.pesan.show = true;
this.pesan.title = pesan;
},
openDialog(id, title = 'dialog', template, data = null, ok = null, extra = null) {
this.dialogs.push({ id, title, visible: true, template: id === 'form' ? template : { template: `<p>${template}</p>` }, data, ok, extra, lebar: id === 'form' ? '410px' : (id === 'confirm' ? '310px' : (id === 'loading' ? '210px' : '310px')) });
},
closeDialog(id) {
const index = this.dialogs.findIndex(dialog => dialog.id === id);
if (index !== -1) {
this.dialogs.splice(index, 1);
}
},
}
});
router.beforeEach((to, from, next) => {
const isAuthenticated = !!app.useraktif;
app.actionButtons=[];
app.floatingButtons={};
if (to.path === '/login' && isAuthenticated) {
next('/dashboard');
} else if (to.path !== '/login' && !isAuthenticated) {
next('/login');
} else {
next();
}
});
const StorageCRUD = (localStorageKey) => {
const getData = () => {
const data = localStorage.getItem(localStorageKey);
return data ? JSON.parse(data) : [];
};
const saveData = (data) => {
localStorage.setItem(localStorageKey, JSON.stringify(data));
};
return {
simpan: (item) => {
try {
const data = getData();
item.id = new Date().getTime();
data.push(item);
saveData(data);
return true;
} catch (error) {
return false;
}
},
hapus: (id) => {
const data = getData();
const initialLength = data.length;
const updatedData = data.filter(item => item.id !== id);
if (updatedData.length < initialLength) {
saveData(updatedData);
return true;
}
return false;
},
edit: (id, updatedItem) => {
const data = getData();
const index = data.findIndex(item => item.id === id);
if (index !== -1) {
data[index] = { ...data[index], ...updatedItem };
saveData(data);
return true;
}
return false;
},
cari: (criteria) => {
const data = getData();
const hasil = data.filter(item => {
return Object.entries(criteria).every(([key, value]) => {
if (Array.isArray(value)) {
return value[0] <= item[key] && item[key] <= value[1];
}
return item[key] && item[key].toString().toLowerCase() === value.toString().toLowerCase();
});
});
return hasil;
},
tampilData: () => {
return getData();
}
};
};
</script>
</body>
</html>