Teknologi
Boilerplate ini menggunakan teknologi :
1. Laravel
-
Versi :
^10.10
-
Laravel adalah kerangka aplikasi web dengan sintaksis yang ekspresif dan elegan. Kami telah meletakkan fondasi yang memungkinkan Anda berkreasi tanpa harus memusingkan hal-hal kecil.
2. Inertia
-
Versi :
^0.6.9
-
Inertia memungkinkan membuat aplikasi satu halaman yang sepenuhnya dirender dari sisi klien, tanpa kerumitan seperti SPA modern.
Inertia berfungsi baik dengan kerangka backend apa pun, tetapi ini disesuaikan untuk Laravel.
3. Vue
-
Versi :
^3.3.4
-
Vue (pengucapan /vjuː/, seperti view) adalah kerangka kerja JavaScript untuk membangun antarmuka pengguna. Itu dibangun di atas HTML standar, CSS, dan JavaScript dan menyediakan model pemrograman deklaratif dan berbasis komponen yang membantu Anda mengembangkan antarmuka pengguna secara efisien, baik yang sederhana maupun yang kompleks.
4. PrimeVue
-
Versi :
^3.33.0
-
PrimeVue adalah sekumpulan komponen UI open source untuk Vue.
Prasyarat
Beberapa prasyarat yang terkait dengan teknologi yang digunakan :
1. Laravel
-
Laravel ini menggunakan bahasa pemrograman PHP dengan versi yang didukung 8.1 s/d 8.2.
-
Laravel ini membutuhkan Composer 2.2.0 atau lebih tinggi.
-
Untuk saat ini, Laravel menyediakan dukungan pihak pertama untuk lima database :
-
MariaDB
-
Versi : 10.10+
-
MySQL
-
Versi : 5.7+
-
PostgreSQL
-
Versi : 11.0+
-
SQLite
-
Versi : 3.8.8+
-
SQL Server
-
Versi : 2017+
-
2. Vue
-
Versi terbaru Vue (3.x) hanya mendukung browser dengan dukungan asli ES2015 yang tidak dapat di-polyfill di browser lama. Ini tidak termasuk IE11 (Internet Explorer).
-
Vue ini membutuhkan Node.js versi 16.0 atau lebih tinggi.
Instalasi
1. Buka Command Prompt di dalam direktori folder Boilerplate tersebut
Masukkan perintah :npm install
package.json
.
composer install
composer.json
.
2. Membuat tautan penyimpanan
Buka Command Prompt di dalam direktori folder Boilerplate tersebut.Masukkan perintah :
php artisan storage:link
3. Mengatur konfigurasi dengan file .env
Terdapat file .env.example
yang ada di direktori file boilerplate.
Salin file tersebut lalu ubah nama menjadi
.env
....
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
...
DB_DATABASE
ganti isi tersebut dengan sesuai database yang digunakan.
4. Memasukkan perintah untuk menghasilkan APP_KEY
dari file .env
Buka Command Prompt di dalam direktori folder Boilerplate tersebut.
Masukkan perintah :
php artisan key:generate
php artisan config:cache
php artisan config:clear
.
5. Memasukkan perintah untuk menghasilkan tabel dari Migrations dan value dari Seeding
Buka Command Prompt di dalam direktori folder Boilerplate tersebut.Masukkan perintah :
php artisan migrate --seed
INFO Preparing database.
Creating migration table ............................................................................... 32ms DONE
INFO Running migrations.
2014_10_12_000000_create_users_table .................................................................. 113ms DONE
2014_10_12_100000_create_password_reset_tokens_table ................................................... 57ms DONE
2014_10_12_200000_add_two_factor_columns_to_users_table ................................................ 29ms DONE
2019_08_19_000000_create_failed_jobs_table ............................................................ 101ms DONE
2019_12_14_000001_create_personal_access_tokens_table .................................................. 66ms DONE
2023_07_05_192703_create_menus_table ................................................................... 19ms DONE
2023_07_05_193609_create_permission_tables ............................................................ 726ms DONE
2023_07_10_233323_create_role_menu_table .............................................................. 135ms DONE
2023_09_15_193537_create_upload_configurations_table ................................................... 91ms DONE
2023_09_15_195254_create_test_upload_table ............................................................. 16ms DONE
INFO Seeding database.
Database\Seeders\UsersTableSeeder ........................................................................ RUNNING
Database\Seeders\UsersTableSeeder ................................................................. 145.89 ms DONE
Database\Seeders\RolesTableSeeder ........................................................................ RUNNING
Database\Seeders\RolesTableSeeder ................................................................. 429.58 ms DONE
Database\Seeders\MenusTableSeeder ........................................................................ RUNNING
Database\Seeders\MenusTableSeeder ................................................................. 130.83 ms DONE
Database\Seeders\UploadConfigurationsTableSeeder ......................................................... RUNNING
Database\Seeders\UploadConfigurationsTableSeeder ................................................... 30.87 ms DONE
Struktur
Struktur folder yang akan sering digunakan :
-
/app/Http/Controllers/
-
Terdapat 2 folder yang akan sering digunakan yaitu
Admin
danPublic
, kedua folder tersebut untuk menampung Controller yang telah dibuat. -
/app/Models/
-
Di dalam folder ini terdapat Models dari tabel-tabel yang telah dibuat.
-
/resources/js/Pages/
-
Terdapat 2 folder yang akan sering digunakan yaitu
Admin
danPublic
, kedua folder tersebut untuk menampung file.Vue
yang telah dibuat. -
/routes/web.php
-
Salah satu file utama untuk mendefinisikan rute (routes) yang digunakan untuk mengatur tampilan antarmuka web.
[project_folder]/
|-- app/
| |-- Http/
| | `-- Controllers/
| | |-- Admin/
| | `-- Public/
| `-- Models/
|-- resources/
| `-- js/
| `-- Pages/
| |-- Admin/
| `-- Public/
`-- routes/
`-- web.php
Penggunaan
Terdapat penggunaan aturan untuk beberapa hal.
Menjalankan Aplikasi
1. Mengatur konfigurasi dengan file .env
Buka file .env
ganti kode APP_URL
isi dengan http://localhost:8000
, maka hasilnya :
....
APP_URL=http://localhost:8000
...
2. Buka Command Prompt di dalam direktori folder Boilerplate tersebut
Masukkan perintah :npm run dev
php artisan serve
serve
hanyalah shortcut atau jalan pintas untuk ke PHP Built-in Webserver, jadi penggunaannya adalah untuk mulai menguji aplikasi secepat mungkin.
PERHATIAN :
Jika ingin menjalankan aplikasi dengan port yang berbeda maka gantiAPP_URL
yang ada di file.env
.
contoh : menggunakan port1234
ganti codeAPP_URL=http://localhost:1234
maka ketika menjalankan perintah di command promptphp artisan serve --port=1234
dan jangan lupa sebelum menjalankan perintahserve
tersebut jalankan perintahphp artisan config:cache
.
Penggunaan Global Variable .vue
Penggunaan Global Variable ini didasarkan dari globalProperties vue.
import { getCurrentInstance } from 'vue';
export default {
...
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
}
};
Untuk saat ini isi dari globalVariable
yang sering digunakan ialah :
-
Variabel ini digunakan untuk memberikan notifikasi di halaman website.
Contoh ketika simpan menu maka akan tampil 'Berhasil Simpan' dibagian ujung atas kanan.
-
Variabel ini digunakan untuk menampilkan Dialog atau Pop up yang bersifat konfirmasi.
Contoh ketika tampil ada pertanyaan 'Apakah anda yakin ingin menghapus data ini?'.
-
Variabel ini digunakan untuk menampilkan Dialog atau Pop up yang berisi formulir, data tabel, dll.
Contoh ketika tampil ada formulir tentang daftar.
-
$t
-
Variabel ini digunakan untuk menggunakan kalimat yang di translate.
Multi Bahasa
Kegunaan untuk proses penyiapan perangkat lunak untuk mendukung bahasa lokal dan pengaturan budaya untuk pasar lain. Produk yang diinternasionalkan mendukung kebutuhan pasar lokal di seluruh dunia, berfungsi lebih tepat berdasarkan norma lokal, dan lebih memenuhi harapan pengguna dalam negeri.
-
/resources/js/i18n.js
-
File utama yang digunakan yaitu
i18n.js
. -
/resources/js/locales/
-
Folder locales untuk file-file bahasa dengan ekstensi
.json
.
Direktori File yang akan sering digunakan
[project_folder]/
`-- resources/
`-- js/
|-- locales/
| `-- ...json
`-- i18n.js
Boilerplate ini sudah ada 2 file bawaan awal bahasa yaitu en.json
dan id.json
.
Contoh pengisian file bahasa :
{
"languages": {
"id": "Indonesia",
"en": "English"
},
"title": {
"config": "English"
}
}
{
"languages": {
"id": "Indonesia",
"en": "English"
},
"title": {
"config": "Indonesia"
}
}
Kegunaan file i18n.js
ini untuk inisialisasi beberapa file bahasa dan untuk mengatur bahasa bawaan awal.
import id from '@/locales/id.json'; //import bahasa indonesia
import en from '@/locales/en.json'; //import bahasa inggris
export const SUPPORT_LOCALES = ['id', 'en']; //daftarkan ke array
...
export default function setupI18n() {
if (!i18n) {
let locale = localStorage.getItem('lang') || 'id'; // pilih bahasa bawaan bahasa awal
i18n = createI18n({
globalInjection: true,
legacy: false,
locale: locale,
fallbackLocale: 'id', // pilih bahasa bawaan bahasa awal
messages: { // daftarkan dari import tersebut
id: id,
en: en,
}
});
setI18nLanguage(locale);
}
return i18n;
}
Penggunaan di file .vue
terdapat contoh yang sudah diterapkan yaitu file AppHeader.vue
dan Beranda.vue
.
-
/resources/js/Layouts/Public/AppHeader.vue
-
Digunakan untuk element HTML mengganti bahasa.
-
/resources/js/Pages/Public/Beranda.vue
-
Contoh implementasi yang akan digunakan.
Direktori File yang dicontohkan
[project_folder]/
`-- resources/
`-- js/
|-- Layouts/
| `-- Public/
| `-- AppHeader.vue
`-- Pages/
`-- Public/
`-- Beranda.vue
<template>
<select v-model="locale">
<option v-for="optionLocale in supportLocales" :key="`locale-${optionLocale}`" :value="optionLocale">
{{ optionLocale }}
</option>
</select>
</template>
<script>
import { useI18n } from 'vue-i18n';
import { SUPPORT_LOCALES as supportLocales, setI18nLanguage } from '@/i18n';
const { locale } = useI18n({ useScope: 'global' });
watch(locale, (val) => {
setI18nLanguage(val);
});
</script>
<template>
<h1>{{ $t('title.config') }}</h1>
</template>
Hasilnya

Tema Public
Public mempunyai 2 tema yaitu Normal dan Landing.
Normal

Landing

Terdapat 4 folder yaitu :
-
Admin
-
Untuk tema admin.
-
Auth
-
Untuk tema autentikasi.
-
Public
-
Untuk tema public ke 1 yaitu normal.
-
Landing
-
Untuk tema public ke 2 yaitu landing.
Direktori Folder Tema
[project_folder]/
`-- resources/
`-- js/
`-- Layouts/
|-- Admin/
|-- Auth/
|-- Public/
`-- Landing/
Untuk file yang dibuat contoh yaitu Beranda.vue
yang direktorinya /resources/js/Pages/Public/Beranda.vue
dengan penggunaan PrimeFlex.
<template>
<div class="grid">
<!-- Contoh pengisian column. Source Penggunaan PrimeFlex -->
<div class="col-8">
<div class="grid">
<div class="col-6">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
6
</div>
</div>
<div class="col-6">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
6
</div>
</div>
<div class="col-12">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
12
</div>
</div>
</div>
</div>
<div class="col-4">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
4
</div>
</div>
<div class="col-12">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
<h1>{{ $t('title.config') }}</h1>
</div>
</div>
<!-- Akhir Contoh -->
</div>
</template>
<script>
import AppLayout from '@/Layouts/Public/AppLayout.vue'; //import layout public component
export default {
layout: AppLayout, // mengatur layout
}
</script>
<template>
<div id="highlights" class="py-4 px-4 lg:px-8 mx-0 my-6 lg:mx-8">
<div class="grid">
<!-- Contoh pengisian column. Source Penggunaan PrimeFlex -->
<div class="col-8">
<div class="grid">
<div class="col-6">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
6
</div>
</div>
<div class="col-6">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
6
</div>
</div>
<div class="col-12">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
12
</div>
</div>
</div>
</div>
<div class="col-4">
<div class="text-center p-3 border-round-sm bg-orange-500 font-bold text-white">
4
</div>
</div>
<!-- Akhir Contoh -->
</div>
</div>
</template>
<script>
import AppLayout from '@/Layouts/Landing/AppLayout.vue'; //import layout Landing component
export default {
layout: AppLayout, // mengatur layout
}
</script>
Login Admin
User yang digunakan untuk mengatur fitur-fitur yang ada ialah :
Admin
Email : admin@gmail.com
Password : admin
Loading Block Document
Kegunaan Loading Block Document ialah untuk mencegah aktivitas pengguna jika halaman website belum selesai memuat data halaman untuk tidak menekan aksi lainnya.
File yang digunakan yaitu loading.js
.
Direktori File
[project_folder]/
`-- resources/
`-- js/
`-- loading.js
Ada 2 cara untuk memakai loading tersebut yaitu :
1. Automatis ketika memuat network
Untuk otomatis ini ada 2 file yang diatur yaitu app.js
dan bootstrap.js
.
Direktori File
[project_folder]/
`-- resources/
`-- js/
|-- app.js
`-- bootstrap.js
-
app.js
-
import {loading,unloading} from '@/loading';
...
InertiaProgress.init()
Inertia.on('start', () => {
loading(); //muat fungsi loading();
})
Inertia.on('finish', () => {
unloading(); //muat fungsi unloading();
})
Kegunaan memberikan loading();
dan unloading();
dibagian tersebut untuk penggunaan request Inertia.
-
bootstrap.js
-
import {loading,unloading} from '@/loading';
import axios from 'axios';
axios.interceptors.request.use((config) => {
// console.log("Start Request XHR");
loading(); //muat fungsi loading();
return config;
}, (error) => {
unloading(); //muat fungsi unloading();
return Promise.reject(error);
});
axios.interceptors.response.use((response) => {
// console.log("Done Request XHR");
unloading(); //muat fungsi unloading();
return response;
}, (error) => {
unloading(); //muat fungsi unloading();
return Promise.reject(error);
});
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
Kegunaan memberikan loading();
dan unloading();
dibagian tersebut untuk penggunaan request Axios.
PERHATIAN :
Untuk saat ini penggunaan Boilerplate menerapkan seperti ini.
2. Manual di file yang dikerjakan .vue
Untuk manual ini ada 2 penggunaan request yaitu Inertia dan Axios.
Inertia
import {loading,unloading} from '@/loading'; //import terlebih dahulu
...
loading(); //muat fungsi loading();
router.post(url, {parameter}, {
onSuccess: () => {
unloading(); //muat fungsi unloading();
},
onError: (error) => {
unloading(); //muat fungsi unloading();
}
});
Axios
import {loading,unloading} from '@/loading'; //import terlebih dahulu
...
loading(); //muat fungsi loading();
axios.post(url, {parameter})
.then((result) => {
unloading(); //muat fungsi unloading();
})
.catch((error) => {
unloading(); //muat fungsi unloading();
});
PERHATIAN :
Adapun penggunaannya dengan cara lain dari component PrimeVue yaitu BlockUI atau bisa dikombinasikan dengan Skeleton.
1. Automatis ketika memuat network
Untuk otomatis ini ada 2 file yang diatur yaituapp.js
dan bootstrap.js
.
Direktori File
[project_folder]/
`-- resources/
`-- js/
|-- app.js
`-- bootstrap.js
-
app.js
-
import {loading,unloading} from '@/loading'; ... InertiaProgress.init() Inertia.on('start', () => { loading(); //muat fungsi loading(); }) Inertia.on('finish', () => { unloading(); //muat fungsi unloading(); })
Kegunaan memberikan
loading();
danunloading();
dibagian tersebut untuk penggunaan request Inertia. -
bootstrap.js
-
import {loading,unloading} from '@/loading'; import axios from 'axios'; axios.interceptors.request.use((config) => { // console.log("Start Request XHR"); loading(); //muat fungsi loading(); return config; }, (error) => { unloading(); //muat fungsi unloading(); return Promise.reject(error); }); axios.interceptors.response.use((response) => { // console.log("Done Request XHR"); unloading(); //muat fungsi unloading(); return response; }, (error) => { unloading(); //muat fungsi unloading(); return Promise.reject(error); }); window.axios = axios; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
Kegunaan memberikan
loading();
danunloading();
dibagian tersebut untuk penggunaan request Axios.
PERHATIAN :
Untuk saat ini penggunaan Boilerplate menerapkan seperti ini.
2. Manual di file yang dikerjakan .vue
Untuk manual ini ada 2 penggunaan request yaitu Inertia dan Axios.
Inertia
import {loading,unloading} from '@/loading'; //import terlebih dahulu
...
loading(); //muat fungsi loading();
router.post(url, {parameter}, {
onSuccess: () => {
unloading(); //muat fungsi unloading();
},
onError: (error) => {
unloading(); //muat fungsi unloading();
}
});
Axios
import {loading,unloading} from '@/loading'; //import terlebih dahulu
...
loading(); //muat fungsi loading();
axios.post(url, {parameter})
.then((result) => {
unloading(); //muat fungsi unloading();
})
.catch((error) => {
unloading(); //muat fungsi unloading();
});
PERHATIAN :
Adapun penggunaannya dengan cara lain dari component PrimeVue yaitu BlockUI atau bisa dikombinasikan dengan Skeleton.
Membuat Data Role
Tampilan Halaman Role

Menu role ini digunakan untuk menambahkan role untuk user dan juga mengatur menu apa saja yang bisa tampil dalam role tersebut.
1. Klik tombol 'Tambah'
Keterangan yang ada diformulir 'Tambah' :-
Role
-
Berguna untuk mengetahui role tersebut apa, contoh : admin, pegawai, client, dll.
-
Menu Layout
-
Terdapat 2 layout Admin dan Public yang berguna untuk Role tersebut aktif di layout apa dan juga layout tersebut untuk menampilkan menu-menu dari layout yang dipilih untuk formulir Pilih Menu.
-
Hak Akses Tambahan
-
Formulir input ini bersifat opsional yang berguna untuk jika ada Hak Akses diluar menu. Contoh diisi
xhrStoreMenu
yang digunakan untuk memberikan perlindungan saat simpan menu hanya di role yang telah diatur. -
Pilih Menu
-
Menu-menu yang tampil ialah menu dengan status Ya yang telah mengisi formulir Apakah Perlu Hak Akses? di menu 'Menu'.
Terdapat :
-
Checkbox :
Jika dipilih maka menu ini akan tampil sesuai role yang diatur. -
Formulir input 'Isi Hak Akses' :
Pengisian ini digunakan untuk hak akses saat tampilnya menu tersebut. ContohbisaMenambah
,bisaMengubah
,bisaMenghapus
, dll.
-
Checkbox :
Formulir Tambah

PERHATIAN :
Rekomendasi pengisian Hak Akses tersebut menggunakan Camel Case. ContohbisaMenambah
,bisaMengubah
,bisaMenghapus
.
2. Penambahan hak akses tersebut di file web.php
Contoh pengisian ->middleware('permission:view');
terdapat 4 cara pengisian yaitu :
-
view
-
Ada pengisian
view
yang berguna untuk menampilkan menu Middleware dari formulir Pilih Menu yang telah dipilih/checkbox. -
Pengisian selain
view
-
Contoh pengisian dari Hak Akses Tambahan yaitu
xhrStoreMenu
ketika penambahan->middleware('permission:xhrStoreMenu');
. -
Pengisian hak akses lebih dari 1
-
Contoh penggabungan dari hak akses
view
danxhrStoreMenu
maka penambahannya->middleware('permission:view|xhrStoreMenu');
.
Route::prefix('admin')->group(function () {
Route::prefix('menu')->group(function () {
Route::get('/', [\App\Http\Controllers\Admin\MenuController::class,'index'])->name('menu')->middleware('permission:view');
});
});
Penggunaan hak akses tersebut di file .vue
Contoh penggunaan hak akses menggunakan fungsi hasAnyPermission()
.
<template>
<div v-if="hasAnyPermission(['bisaMengatur'])">
</div>
</template>
Menambahkan Role Ke User
Disini contoh menambahkan role ke user ada menu 'Users'.
Tampilan Halaman users

Klik tombol 'Tambah'
Di formulir tambah User terdapat formulir input Roles (kotak merah) yang dimana bisa dipilih lebih dari 1 Roles tetapi tidak bisa layout yang berbeda.
Jadi jika memilih lebih dari 1 Roles maka menu akan bergabung.
Contoh Roles:
-
Gudang
-
Memiliki menu yang dipilih :
- A
- B
-
Penjual
-
Memiliki menu yang dipilih :
- C
- D
-
Jika dipilih 2 roles tersebut
-
Menu yang tampil di user tersebut :
- A
- B
- C
- D
Formulir Tambah

Penggunaan implementasi Roles ke User tersebut di file .php
$dataUser->syncRoles([id_roles,id_roles,id_roles]);
Membuat Data Upload Configurations
Tampilan Halaman Upload Configurations

Menu Upload Configurations ini digunakan untuk menambahkan data aturan upload.
PERHATIAN
Menambah atau mengubah ekstensi terdapat di file UploadController.php
.
Direktori File UploadController.php
[project_folder]/
`-- app/
`-- Http/
`-- Controllers/
`-- Admin/
`-- UploadController.php
Letak penambahan tersebut di fungsi index()
variabel $dataEkstensi
.
public function index()
{
...
$dataEkstensi = [
".jpg",
".psd",
".docx",
".xls",
".xlsx",
".zip",
".doc",
".bmp",
".pdf",
".ppt",
".png",
".rar",
".jpeg",
];
...
}
Klik tombol 'Tambah'
Keterangan yang ada diformulir 'Tambah' :-
Menu
-
Berguna untuk mengetahui aturan upload ini berada di menu apa.
-
Keterangan
-
Berguna untuk mengisi keterangan atura upload tersebut contoh upload tentang administrasi, dll.
-
Ekstensi
-
Terdapat ekstensi pilihan yang telah diatur sebelumnya di file
UploadController.php
dan juga terdapat formulir input cari ekstensi. -
Ukuran
-
Berguna untuk mengatur ukuran file yang diupload.
Pengisian formulir input tersebut menggunakan satuan Kilobyte (Kb) dan ada info bagian kanan input untuk menghitung berapa besar ukuran yang diisi dengan maksimal 953.67 Gb.
Formulir Tambah

Contoh Upload Single Basic
Penggunaan contoh ini ada di menu 'Test Upload'.
Contoh Tampilan Formulir

Terdapat 2 file yang harus ditambahkan kode untuk mengupload yaitu file .vue
dan .php
.
Contoh penggunaan ini menggunakan tabel test_upload
dengan code di file TestUpload.php
, Upload.vue
dan UploadController.php
.
Direktori File
[project_folder]/
|-- app/
| |-- Http/
| | `-- Controllers/
| | `-- Admin/
| | `-- UploadController.php
| `-- Models/
| `-- TestUpload.php
`-- resources/
`-- js/
`-- Pages/
`-- TestUpload/
`-- Upload.vue
File TestUpload.php
Kolom yang digunakan untuk menyimpan data ialah file1
.
Terdapat 3 kode tambahan khusus yaitu :
-
locationFile()
-
Fungsi ini berguna untuk menempatkan file yang di upload folder apa.
-
$appends
-
Tambah kolom
file1_download
untuk link download file tersebut. -
getFile1DownloadAttribute()
-
Fungsi ini berguna untuk menggantikan isi default kolom
file1_download
dengan letak file tersebut.
...
class TestUpload extends Model
{
...
public static function locationFile()
{
return "test_upload";
}
protected $appends = [
'file1_download',
];
public function getFile1DownloadAttribute()
{
return asset('/storage/' . $this->locationFile() . '/' . $this->attributes['file1']);
}
...
}
File Upload.vue
<template>
<small>
{{ data.uploadSingleBasic.information.ekstensi }} /
Maks : {{ data.uploadSingleBasic.information.ukuran_tampil }}
</small>
<FileUpload
mode="basic"
name="files[]"
:accept="data.uploadSingleBasic.information.ekstensi"
:max-file-size="(data.uploadSingleBasic.information.ukuran / 1.024) * 1000"
:custom-upload="true"
choose-label="Upload File"
@uploader="methods.fileupload.uploader"
@select="methods.fileupload.select" />
<a :href="data.file1_download" target="_blank" download :title="data.file1_download">
<Button
:label="'Download (' + data.file1 + ')'"
severity="primary"
:href="data.file1_download" />
</a>
</template>
<script>
import { reactive, onMounted, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { Upload} from '@/utils';
export default {
...
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
let id_upload = 1;
const data = reactive({
uploadSingleBasic: {
init: new Upload(id_upload),
information: {},
},
});
const form = reactive({
fileUploadSingleBasic: [],
});
onMounted(async () => {
data.uploadSingleBasic.information = await data.uploadSingleBasic.init.information();
});
const methods = {
fileupload: {
select: (event) => {
let dataFiles = new FormData();
dataFiles.append('files[]', event.originalEvent.target.files[0]);
data.uploadSingleBasic.init.check(dataFiles).then((value) => {
if (value === true) {
form.fileUploadSingleBasic.push(event.files[0]);
}
else {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: value,
life: 3000 });
}
});
},
uploader: () => {
router.post(route("storeTestUpload"),
{
files: form.fileUploadSingleBasic,
id_file_configurations: id_upload,
}
, {
forceFormData: true,
preserveState: true,
onSuccess: () => {
globalVariable.$toast.add({ severity: 'success',
summary: 'SUKSES',
detail: 'Berhasil Simpan',
life: 3000 });
form.fileUploadSingleBasic = [];
},
onError: (error) => {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: error[Object.keys(error)[0]],
life: 3000 });
return;
}
});
},
},
}
};
return {
form,
methods,
data
};
}
};
</script>
Beberapa kode yang perlu diperhatikan :
-
Penggunaan component ini dari PrimeVue.
-
mode="basic"
-
Mendefinisikan UI komponen, value yang bisa digunakan adalah
advanced
danbasic
. -
name="files[]"
-
Berguna saat nanti mengisi value ke FormData.
-
:accept="data.uploadSingleBasic.information.ekstensi"
-
Menampilkan ekstensi upload yang diizinkan seperti
image/*
. -
:max-file-size="(data.uploadSingleBasic.information.ukuran / 1.024) * 1000"
-
Ukuran file maksimum yang diperbolehkan dalam byte.
-
:custom-upload="true"
-
Apakah akan menggunakan unggahan default atau implementasi manual yang ditentukan dalam fungsi
uploadHandler
. -
choose-label="Upload File"
-
Kegunaan ini untuk mengganti tulisan tombol upload.
-
@uploader="methods.fileupload.uploader"
-
Pemanggilan fungsi custom upload.
-
@select="methods.fileupload.select"
-
Pemanggilan fungsi ketika file telah dipilih.
-
-
const data = ...
-
Variabel
data
ini berguna untuk menampung data upload dariinit: new Upload(id_upload)
daninformation
sebagai informasi ektensi, ukuran, dll. -
const form = ...
-
Variabel
form
ini berguna untuk menampung data yang telah di upload. -
onMounted(async () ...
-
Fungsi ini digunakan saat halaman selesai memuat dengan Asynchronous untuk mendapatkan informasi upload.
-
select: (event) => { ...
-
Fungsi ini digunakan ketika selesai memilih file dengan menggunakan fungsi
data.uploadSingleBasic.init.check(
jika benar maka push file jika salah akan muncul notifikasi. -
uploader: () => { ...
-
Fungsi ini digunakan ketika simpan data akan mengirim files yang telah di upload dan id_upload.
File UploadController.php
-
CoreUploadController;
-
Digunakan untuk menggunakan class lain untuk upload dan cek.
-
if(...->checkWithArguments(...
-
Fungsi ini digunakan untuk cek file yang di upload lagi dengan parameter
kolom
yang di request,files
, danid_upload
. -
$dataUploaded = ...->uploads(...
-
Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request
file
danlokasi folder
dengan hasil akhir array. -
Storage::disk('local')->delete(...
-
Fungsi ini berguna untuk menghapus file dengan lokasi direktori file yang telah di upload.
use App\Http\Controllers\Core\UploadController as CoreUploadController;
use App\Models\TestUpload;
class UploadController extends Controller
{
...
public function storeTestUpload(Request $request)
{
....
if ((new CoreUploadController())->checkWithArguments("files", $request->file(), $request->id_file_configurations)) {
$dataUploaded = (new CoreUploadController)->uploads($request->file("files"), TestUpload::locationFile());
$obj = [
"file1" => $dataUploaded[0],
];
TestUpload::updateOrCreate(['id' => $request->data['id'] ?? null], $obj);
}
...
}
public function deleteTestUpload()
{
...
Storage::disk('local')->delete('public/' . TestUpload::locationFile() . '/' . request()->data['file1']);
...
}
}
Hasilnya

Contoh Upload Single Input
Penggunaan contoh ini ada di menu 'Test Upload'.
Contoh Tampilan Formulir

Terdapat 2 file yang harus ditambahkan kode untuk mengupload yaitu file .vue
dan .php
.
Contoh penggunaan ini menggunakan tabel test_upload
dengan code di file TestUpload.php
, Upload.vue
dan UploadController.php
.
Direktori File
[project_folder]/
|-- app/
| |-- Http/
| | `-- Controllers/
| | `-- Admin/
| | `-- UploadController.php
| `-- Models/
| `-- TestUpload.php
`-- resources/
`-- js/
`-- Pages/
`-- TestUpload/
`-- Upload.vue
File TestUpload.php
Kolom yang digunakan untuk menyimpan data ialah file1
.
Terdapat 3 kode tambahan khusus yaitu :
-
locationFile()
-
Fungsi ini berguna untuk menempatkan file yang di upload folder apa.
-
$appends
-
Tambah kolom
file1_download
untuk link download file tersebut. -
getFile1DownloadAttribute()
-
Fungsi ini berguna untuk menggantikan isi default kolom
file1_download
dengan letak file tersebut.
...
class TestUpload extends Model
{
...
public static function locationFile()
{
return "test_upload";
}
protected $appends = [
'file1_download',
];
public function getFile1DownloadAttribute()
{
return asset('/storage/' . $this->locationFile() . '/' . $this->attributes['file1']);
}
...
}
File Upload.vue
<template>
<small>
{{ data.uploadSingleInput.information.ekstensi }} /
Maks : {{ data.uploadSingleInput.information.ukuran_tampil }}
</small>
<div class="p-inputgroup flex-1">
<InputText
placeholder="File Name"
disabled
id="file_name"
:value="data.dataSingleInput[0] != undefined ? data.dataSingleInput[0].file1 : ''" />
<FileUpload
mode="basic"
name="files[]"
:auto="true"
class="border-noround-left"
:accept="data.uploadSingleInput.information.ekstensi"
:max-file-size="(data.uploadSingleInput.information.ukuran / 1.024) * 1000"
:custom-upload="true"
choose-label="Upload File"
@select="methods.fileupload.select" />
</div>
<Button label="Simpan" severity="success" @click="methods.fileupload.click" />
<a :href="data.file1_download" target="_blank" download :title="data.file1_download">
<Button
:label="'Download (' + data.file1 + ')'"
severity="primary"
:href="data.file1_download" />
</a>
</template>
<script>
import { reactive, onMounted, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { Upload} from '@/utils';
export default {
...
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
let id_upload = 2;
const data = reactive({
uploadSingleInput: {
init: new Upload(id_upload),
information: {},
},
});
const form = reactive({
fileUploadSingleInput: [],
});
onMounted(async () => {
data.uploadSingleInput.information = await data.uploadSingleInput.init.information();
});
const methods = {
fileupload: {
select: (event) => {
let dataFiles = new FormData();
dataFiles.append('files[]', event.originalEvent.target.files[0]);
data.uploadSingleInput.init.check(dataFiles).then((value) => {
if (value === true) {
document.getElementById("file_name").value = event.files[0].name;
form.fileUploadSingleInput.push(event.files[0]);
}
else {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: value,
life: 3000 });
}
});
},
click: () => {
router.post(route("storeTestUpload"),
{
files: form.fileUploadSingleInput,
id_file_configurations: id_upload
}
, {
forceFormData: true,
preserveState: true,
onSuccess: () => {
globalVariable.$toast.add({ severity: 'success',
summary: 'SUKSES',
detail: 'Berhasil Simpan',
life: 3000 });
form.fileUploadSingleInput = [];
},
onError: (error) => {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: error[Object.keys(error)[0]],
life: 3000 });
return;
}
});
},
},
}
};
return {
form,
methods,
data
};
}
};
</script>
Beberapa kode yang perlu diperhatikan :
-
Penggunaan component ini dari PrimeVue.
-
disabled
-
Input secara default akan ter-disabled karena tidak digunakan untuk pengisian.
-
id="file_name"
-
Kegunaan
id
ini berguna untuk setelah file dipilih maka nama file tersebut akan tercantum di InputText.
-
-
Penggunaan component ini dari PrimeVue.
-
mode="basic"
-
Mendefinisikan UI komponen, value yang bisa digunakan adalah
advanced
danbasic
. -
name="files[]"
-
Berguna saat nanti mengisi value ke FormData.
-
:auto="true"
-
Jika diaktifkan, ketika upload dimulai secara otomatis setelah pemilihan selesai.
-
class="border-noround-left"
-
Class ini digunakan untuk menghilangkan border bagian ujung kiri atas dan bawah.
-
:accept="data.uploadSingleInput.information.ekstensi"
-
Menampilkan ekstensi upload yang diizinkan seperti
image/*
. -
:max-file-size="(data.uploadSingleInput.information.ukuran / 1.024) * 1000"
-
Ukuran file maksimum yang diperbolehkan dalam byte.
-
:custom-upload="true"
-
Apakah akan menggunakan unggahan default atau implementasi manual yang ditentukan dalam fungsi
uploadHandler
. -
choose-label="Upload File"
-
Kegunaan ini untuk mengganti tulisan tombol upload.
-
@select="methods.fileupload.select"
-
Pemanggilan fungsi ketika file telah dipilih.
-
-
const data = ...
-
Variabel
data
ini berguna untuk menampung data upload dariinit: new Upload(id_upload)
daninformation
sebagai informasi ektensi, ukuran, dll. -
const form = ...
-
Variabel
form
ini berguna untuk menampung data yang telah di upload. -
onMounted(async () ...
-
Fungsi ini digunakan saat halaman selesai memuat dengan Asynchronous untuk mendapatkan informasi upload.
-
select: (event) => { ...
-
Fungsi ini digunakan ketika selesai memilih file dengan menggunakan fungsi
data.uploadSingleBasic.init.check(
jika benar maka push file jika salah akan muncul notifikasi. -
document.getElementById("file_name").value = event.files[0].name;
-
Berguna untuk mengisi value dari nama file yang telah di upload.
-
click: () => { ...
-
Fungsi ini digunakan ketika simpan data akan mengirim files yang telah di upload dan id_upload.
File UploadController.php
-
CoreUploadController;
-
Digunakan untuk menggunakan class lain untuk upload dan cek.
-
if(...->checkWithArguments(...
-
Fungsi ini digunakan untuk cek file yang di upload lagi dengan parameter
kolom
yang di request,files
, danid_upload
. -
$dataUploaded = ...->uploads(...
-
Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request
file
danlokasi folder
dengan hasil akhir array. -
Storage::disk('local')->delete(...
-
Fungsi ini berguna untuk menghapus file dengan lokasi direktori file yang telah di upload.
use App\Http\Controllers\Core\UploadController as CoreUploadController;
use App\Models\TestUpload;
class UploadController extends Controller
{
...
public function storeTestUpload(Request $request)
{
....
if ((new CoreUploadController())->checkWithArguments("files", $request->file(), $request->id_file_configurations)) {
$dataUploaded = (new CoreUploadController)->uploads($request->file("files"), TestUpload::locationFile());
$obj = [
"file1" => $dataUploaded[0],
];
TestUpload::updateOrCreate(['id' => $request->data['id'] ?? null], $obj);
}
...
}
public function deleteTestUpload()
{
...
Storage::disk('local')->delete('public/' . TestUpload::locationFile() . '/' . request()->data['file1']);
...
}
}
Hasilnya

Contoh Upload Single In Multiple Data (Table)
Penggunaan contoh ini ada di menu 'Test Upload'.
Contoh Tampilan Formulir

Terdapat 2 file yang harus ditambahkan kode untuk mengupload yaitu file .vue
dan .php
.
Contoh penggunaan ini menggunakan tabel test_upload
dengan code di file TestUpload.php
, Upload.vue
dan UploadController.php
.
Direktori File
[project_folder]/
|-- app/
| |-- Http/
| | `-- Controllers/
| | `-- Admin/
| | `-- UploadController.php
| `-- Models/
| `-- TestUpload.php
`-- resources/
`-- js/
`-- Pages/
`-- TestUpload/
`-- Upload.vue
File TestUpload.php
Kolom yang digunakan untuk menyimpan data ialah file1
.
Terdapat 3 kode tambahan khusus yaitu :
-
locationFile()
-
Fungsi ini berguna untuk menempatkan file yang di upload folder apa.
-
$appends
-
Tambah kolom
file1_download
untuk link download file tersebut. -
getFile1DownloadAttribute()
-
Fungsi ini berguna untuk menggantikan isi default kolom
file1_download
dengan letak file tersebut.
...
class TestUpload extends Model
{
...
public static function locationFile()
{
return "test_upload";
}
protected $appends = [
'file1_download',
];
public function getFile1DownloadAttribute()
{
return asset('/storage/' . $this->locationFile() . '/' . $this->attributes['file1']);
}
...
}
File Upload.vue
<template>
<small>
{{ data.uploadSingleInMultipleData.information.ekstensi }} /
Maks : {{ data.uploadSingleInMultipleData.information.ukuran_tampil }}
</small>
<FileUpload
ref="refFileUploadSingleInMultipleData"
mode="basic"
name="files[]"
:accept="data.uploadSingleInMultipleData.information.ekstensi"
:max-file-size="(data.uploadSingleInMultipleData.information.ukuran / 1.024) * 1000"
:custom-upload="true"
choose-label="Upload image"
@uploader="methods.fileupload.uploader"
@select="methods.fileupload.select" />
<DataTable :value="form.fileUploadSingleInMultipleData" showGridlines tableStyle="min-width: 50rem">
<template #empty>
<div class="text-center">Tidak ada data</div>
</template>
<Column style="text-align: center;">
<template #header> <span class="flex-1 text-center">No.</span> </template>
<template #body="{ frozenRow, index }">
{{ index + 1 }}
</template>
</Column>
<Column>
<template #header> <span class="flex-1 text-center">Name</span> </template>
<template #body="{ data, frozenRow, index }">
<div class="text-center">
<span>{{ data.name }}</span>
</div>
</template>
</Column>
<Column>
<template #header> <span class="flex-1 text-center">Size</span> </template>
<template #body="{ data, frozenRow, index }">
<div class="text-center">
<span>{{ data.size }}</span>
</div>
</template>
</Column>
</DataTable>
<Button
label="Simpan"
severity="success"
@click="methods.fileupload.click" />
</template>
<script>
import { reactive, onMounted, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { Upload} from '@/utils';
export default {
...
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
let id_upload = 3;
const data = reactive({
uploadSingleInMultipleData: {
init: new Upload(id_upload),
information: {},
},
});
const form = reactive({
fileUploadSingleInMultipleData: [],
});
onMounted(async () => {
data.uploadSingleInMultipleData.information = await data.uploadSingleInMultipleData.init.information();
});
const refFileUploadSingleInMultipleData = ref(null);
const methods = {
fileupload: {
select: (event) => {
let dataFiles = new FormData();
dataFiles.append('files[]', event.originalEvent.target.files[0]);
var statusDuplicate = form.fileUploadSingleInMultipleData.filter(item => {
return item.name == event.originalEvent.target.files[0].name;
})
if (statusDuplicate.length != 0) {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: "Data Sudah Ada",
life: 3000 });
refFileUploadSingleInMultipleData.value.clear();
return;
}
data.uploadSingleInMultipleData.init.check(dataFiles).then((value) => {
if (!(value === true)) {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: value,
life: 3000 });
return;
}
});
},
uploader: (event) => {
form.fileUploadSingleInMultipleData.push(event.files[0]);
},
click: () => {
router.post(route("storeTestUpload"),
{
data_file: form.fileUploadSingleInMultipleData,
id_file_configurations: id_upload,
}
, {
forceFormData: true,
preserveState: true,
onSuccess: () => {
globalVariable.$toast.add({ severity: 'success',
summary: 'SUKSES',
detail: 'Berhasil Simpan',
life: 3000 });
},
onError: (error) => {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: error[Object.keys(error)[0]],
life: 3000 });
return;
}
});
},
},
}
};
return {
refFileUploadSingleInMultipleData,
form,
methods,
data
};
}
};
</script>
Beberapa kode yang perlu diperhatikan :
-
Penggunaan component ini dari PrimeVue.
-
ref="refFileUploadSingleInMultipleData"
-
ref
adalah atribut khusus, mirip dengan atributkey
yang dibahasv-for
.Hal ini memungkinkan kita untuk mendapatkan referensi langsung ke elemen DOM tertentu atau instance komponen turunan setelah elemen tersebut dipasang.
-
mode="basic"
-
Mendefinisikan UI komponen, value yang bisa digunakan adalah
advanced
danbasic
. -
name="files[]"
-
Berguna saat nanti mengisi value ke FormData.
-
:accept="data.uploadSingleInMultipleData.information.ekstensi"
-
Menampilkan ekstensi upload yang diizinkan seperti
image/*
. -
:max-file-size="(data.uploadSingleInMultipleData.information.ukuran / 1.024) * 1000"
-
Ukuran file maksimum yang diperbolehkan dalam byte.
-
:custom-upload="true"
-
Apakah akan menggunakan unggahan default atau implementasi manual yang ditentukan dalam fungsi
uploadHandler
. -
choose-label="Upload File"
-
Kegunaan ini untuk mengganti tulisan tombol upload.
-
@uploader="methods.fileupload.uploader"
-
Pemanggilan fungsi custom upload.
-
@select="methods.fileupload.select"
-
Pemanggilan fungsi ketika file telah dipilih.
-
-
Berguna untuk menampilkan data file yang telah dipilih dalam bentuk tabel.
-
const data = ...
-
Variabel
data
ini berguna untuk menampung data upload dariinit: new Upload(id_upload)
daninformation
sebagai informasi ektensi, ukuran, dll. -
const form = ...
-
Variabel
form
ini berguna untuk menampung data yang telah di upload. -
onMounted(async () ...
-
Fungsi ini digunakan saat halaman selesai memuat dengan Asynchronous untuk mendapatkan informasi upload.
-
select: (event) => { ...
-
Fungsi ini digunakan ketika selesai memilih file dengan menggunakan fungsi
data.uploadSingleBasic.init.check(
jika benar maka push file jika salah akan muncul notifikasi. -
refFileUploadSingleInMultipleData.value.clear();
-
Menghapus file tanpa di upload.
-
uploader: (event) => { ...
-
Berguna untuk menambahkan data file ke array.
-
click: () => { ...
-
Fungsi ini digunakan ketika simpan data akan mengirim files yang telah di upload dan id_upload.
File UploadController.php
-
CoreUploadController;
-
Digunakan untuk menggunakan class lain untuk upload dan cek.
-
if(...->checkWithArguments(...
-
Fungsi ini digunakan untuk cek file yang di upload lagi dengan parameter
kolom
yang di request,files
, danid_upload
. -
$dataUploaded = ...->uploads(...
-
Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request
file
danlokasi folder
dengan hasil akhir array. -
Storage::disk('local')->delete(...
-
Fungsi ini berguna untuk menghapus file dengan lokasi direktori file yang telah di upload.
use App\Http\Controllers\Core\UploadController as CoreUploadController;
use App\Models\TestUpload;
class UploadController extends Controller
{
...
public function storeTestUpload(Request $request)
{
....
if (count($request->file()) != 0) {
if ((new CoreUploadController())->checkWithArguments("data_file", $request->file(), $request->id_file_configurations)) {
$dataUploaded = (new CoreUploadController)->uploads($request->file("data_file"), TestUpload::locationFile());
foreach ($dataUploaded as $key => $value) {
$obj = [
"file1" => $value,
];
TestUpload::insert($obj);
}
}
}
...
}
public function deleteTestUpload()
{
...
Storage::disk('local')->delete('public/' . TestUpload::locationFile() . '/' . request()->data['file1']);
...
}
}
Hasilnya

PERHATIAN :
Jika penggunaan upload tersebut menggunakanmultiple
maka tidak perlu menggunakanindex
langsungevent.files
.
Contoh Upload Single Object
Penggunaan contoh ini ada di menu 'Test Upload'.
Contoh Tampilan Formulir

Contoh penggunaan Upload Single Object ini digunakan jika ada file yang tidak diisi
Terdapat 2 file yang harus ditambahkan kode untuk mengupload yaitu file .vue
dan .php
.
Contoh penggunaan ini menggunakan tabel test_upload
dengan code di file TestUpload.php
, Upload.vue
dan UploadController.php
.
Direktori File
[project_folder]/
|-- app/
| |-- Http/
| | `-- Controllers/
| | `-- Admin/
| | `-- UploadController.php
| `-- Models/
| `-- TestUpload.php
`-- resources/
`-- js/
`-- Pages/
`-- TestUpload/
`-- Upload.vue
File TestUpload.php
Kolom yang digunakan untuk menyimpan data ialah file1
.
Terdapat 3 kode tambahan khusus yaitu :
-
locationFile()
-
Fungsi ini berguna untuk menempatkan file yang di upload folder apa.
-
$appends
-
Tambah kolom
file1_download, file2_download
danfile3_download
untuk link download file tersebut. -
getFile1DownloadAttribute(), getFile2DownloadAttribute(), getFile3DownloadAttribute()
-
Fungsi ini berguna untuk menggantikan isi default kolom
file1_download, file2_download
danfile3_download
dengan letak file tersebut.
...
class TestUpload extends Model
{
...
public static function locationFile()
{
return "test_upload";
}
protected $appends = [
'file1_download',
'file2_download',
'file3_download',
];
public function getFile1DownloadAttribute()
{
return asset('/storage/' . $this->locationFile() . '/' . $this->attributes['file1']);
}
public function getFile2DownloadAttribute()
{
return asset('/storage/' . $this->locationFile() . '/' . $this->attributes['file2']);
}
public function getFile3DownloadAttribute()
{
return asset('/storage/' . $this->locationFile() . '/' . $this->attributes['file3']);
}
...
}
File Upload.vue
<template>
<div class="formgrid grid">
<div class="field col-4">
<label for="role">test 1 </label>
<br>
<small>
{{ data.uploadSingleObject1.information.ekstensi }} /
Maks : {{ data.uploadSingleObject1.information.ukuran_tampil }}
</small>
<div class="p-inputgroup flex-1">
<InputText
placeholder="File Name"
disabled
id="file_name_object1"
:value="data.dataSingleObject[0] != undefined ? data.dataSingleObject[0].file1 : ''" />
<FileUpload
mode="basic"
name="files[]"
:auto="true"
class="border-noround-left"
:accept="data.uploadSingleObject1.information.ekstensi"
:max-file-size="(data.uploadSingleObject1.information.ukuran / 1.024) * 1000"
:custom-upload="true"
choose-label="1"
@select="methods.fileupload.select1" />
</div>
</div>
<div class="field col-4">
<label for="role">test 2 </label>
<br>
<small>
{{ data.uploadSingleObject2.information.ekstensi }} /
Maks : {{ data.uploadSingleObject2.information.ukuran_tampil }}
</small>
<div class="p-inputgroup flex-1">
<InputText
placeholder="File Name"
disabled
id="file_name_object2"
:value="data.dataSingleObject[0] != undefined ? data.dataSingleObject[0].file2 : ''" />
<FileUpload
mode="basic"
name="files[]"
:auto="true"
class="border-noround-left"
:accept="data.uploadSingleObject2.information.ekstensi"
:max-file-size="(data.uploadSingleObject2.information.ukuran / 1.024) * 1000"
:custom-upload="true"
choose-label="2"
@select="methods.fileupload.select2" />
</div>
</div>
<div class="field col-4">
<label for="role">test 3 </label>
<br>
<small>
{{ data.uploadSingleObject3.information.ekstensi }} /
Maks : {{ data.uploadSingleObject3.information.ukuran_tampil }}
</small>
<div class="p-inputgroup flex-1">
<InputText
placeholder="File Name"
disabled
id="file_name_object3"
:value="data.dataSingleObject[0] != undefined ? data.dataSingleObject[0].file3 : ''" />
<FileUpload
mode="basic"
name="files[]"
:auto="true"
class="border-noround-left"
:accept="data.uploadSingleObject3.information.ekstensi"
:max-file-size="(data.uploadSingleObject3.information.ukuran / 1.024) * 1000"
:custom-upload="true"
choose-label="3"
@select="methods.fileupload.select3" />
</div>
</div>
</div>
<Button
label="Simpan"
severity="success"
@click="methods.fileupload.click" />
</template>
<script>
import { reactive, onMounted, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { Upload} from '@/utils';
export default {
...
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
let id_upload1 = 4;
let id_upload2 = 5;
let id_upload3 = 6;
const data = reactive({
uploadSingleObject1: {
init: new Upload(id_upload1),
information: {},
},
uploadSingleObject2: {
init: new Upload(id_upload2),
information: {},
},
uploadSingleObject3: {
init: new Upload(id_upload3),
information: {},
},
});
const form = reactive({
fileUploadSingleObject: {
satu: [],
dua: [],
tiga: [],
},
});
onMounted(async () => {
data.uploadSingleObject1.information = await data.uploadSingleObject1.init.information();
data.uploadSingleObject2.information = await data.uploadSingleObject2.init.information();
data.uploadSingleObject3.information = await data.uploadSingleObject3.init.information();
});
const methods = {
fileupload: {
select1: (event) => {
let dataFiles = new FormData();
dataFiles.append('files[]', event.originalEvent.target.files[0]);
data.uploadSingleObject1.init.check(dataFiles).then((value) => {
if (value === true) {
document.getElementById("file_name_object1").value = event.files[0].name;
form.fileUploadSingleObject.satu.push(event.files[0]);
}
else {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: value,
life: 3000 });
}
});
},
select2: (event) => {
let dataFiles = new FormData();
dataFiles.append('files[]', event.originalEvent.target.files[0]);
data.uploadSingleObject2.init.check(dataFiles).then((value) => {
if (value === true) {
document.getElementById("file_name_object2").value = event.files[0].name;
form.fileUploadSingleObject.dua.push(event.files[0]);
}
else {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: value,
life: 3000 });
}
});
},
select3: (event) => {
let dataFiles = new FormData();
dataFiles.append('files[]', event.originalEvent.target.files[0]);
data.uploadSingleObject3.init.check(dataFiles).then((value) => {
if (value === true) {
document.getElementById("file_name_object3").value = event.files[0].name;
form.fileUploadSingleObject.tiga.push(event.files[0]);
}
else {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: value,
life: 3000 });
}
});
},
click: () => {
router.post(route("storeTestUpload"),
{
files: form.fileUploadSingleObject,
id_file_configurations_object1: id_upload1,
id_file_configurations_object2: id_upload2,
id_file_configurations_object3: id_upload3,
}
, {
forceFormData: true,
preserveState: true,
onSuccess: () => {
globalVariable.$toast.add({ severity: 'success',
summary: 'SUKSES',
detail: 'Berhasil Simpan',
life: 3000 });
form.fileUploadSingleObject = {
satu: [],
dua: [],
tiga: [],
};
},
onError: (error) => {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: error[Object.keys(error)[0]],
life: 3000 });
return;
}
});
}
},
}
};
return {
form,
methods,
data
};
}
};
</script>
Beberapa kode yang perlu diperhatikan :
-
Penggunaan component ini dari PrimeVue.
-
disabled
-
Input secara default akan ter-disabled karena tidak digunakan untuk pengisian.
-
id="file_name..."
-
Kegunaan
id
ini berguna untuk setelah file dipilih maka nama file tersebut akan tercantum di InputText.
-
-
Penggunaan component ini dari PrimeVue.
-
mode="basic"
-
Mendefinisikan UI komponen, value yang bisa digunakan adalah
advanced
danbasic
. -
name="files[]"
-
Berguna saat nanti mengisi value ke FormData.
-
:auto="true"
-
Jika diaktifkan, ketika upload dimulai secara otomatis setelah pemilihan selesai.
-
class="border-noround-left"
-
Class ini digunakan untuk menghilangkan border bagian ujung kiri atas dan bawah.
-
:accept="data.uploadSingleInput.information.ekstensi"
-
Menampilkan ekstensi upload yang diizinkan seperti
image/*
. -
:max-file-size="(data.uploadSingleInput.information.ukuran / 1.024) * 1000"
-
Ukuran file maksimum yang diperbolehkan dalam byte.
-
:custom-upload="true"
-
Apakah akan menggunakan unggahan default atau implementasi manual yang ditentukan dalam fungsi
uploadHandler
. -
choose-label="Upload File"
-
Kegunaan ini untuk mengganti tulisan tombol upload.
-
@select="methods.fileupload.select"
-
Pemanggilan fungsi ketika file telah dipilih.
-
-
const data = ...
-
Variabel
data
ini berguna untuk menampung data upload dariinit: new Upload(id_upload)
daninformation
sebagai informasi ektensi, ukuran, dll. -
const form = ...
-
Variabel
form
ini berguna untuk menampung data yang telah di upload. -
onMounted(async () ...
-
Fungsi ini digunakan saat halaman selesai memuat dengan Asynchronous untuk mendapatkan informasi upload.
-
select ... : (event) => { ...
-
Fungsi ini digunakan ketika selesai memilih file dengan menggunakan fungsi
... .init.check(
jika benar maka push file jika salah akan muncul notifikasi. -
document.getElementById("file_name ...").value = event.files[0].name;
-
Berguna untuk mengisi value dari nama file yang telah di upload.
-
click: () => { ...
-
Fungsi ini digunakan ketika simpan data akan mengirim files yang telah di upload dan id_upload.
File UploadController.php
-
CoreUploadController;
-
Digunakan untuk menggunakan class lain untuk upload dan cek.
-
if(...->checkWithArguments(...
-
Fungsi ini digunakan untuk cek file yang di upload lagi dengan parameter
kolom
yang di request,files
, danid_upload
. -
$dataUploaded = ...->uploads(...
-
Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request
file
danlokasi folder
dengan hasil akhir array. -
Storage::disk('local')->delete(...
-
Fungsi ini berguna untuk menghapus file dengan lokasi direktori file yang telah di upload.
use App\Http\Controllers\Core\UploadController as CoreUploadController;
use App\Models\TestUpload;
class UploadController extends Controller
{
...
public function storeTestUpload(Request $request)
{
....
if (!empty($request->file("files")["satu"])) {
if ((new CoreUploadController())->checkWithArguments("satu", $request->file("files"), $request->id_file_configurations_object1)) {
$dataUploaded1 = (new CoreUploadController)->uploads($request->file("files")['satu'], TestUpload::locationFile());
$dataFile1 = $dataUploaded1[0];
}
}
if (!empty($request->file("files")["dua"])) {
if ((new CoreUploadController())->checkWithArguments("dua", $request->file("files"), $request->id_file_configurations_object2)) {
$dataUploaded2 = (new CoreUploadController)->uploads($request->file("files")['dua'], TestUpload::locationFile());
$dataFile2 = $dataUploaded2[0];
}
}
if (!empty($request->file("files")["tiga"])) {
if ((new CoreUploadController())->checkWithArguments("tiga", $request->file("files"), $request->id_file_configurations_object3)) {
$dataUploaded3 = (new CoreUploadController)->uploads($request->file("files")['tiga'], TestUpload::locationFile());
$dataFile3 = $dataUploaded3[0];
}
}
$obj = [
"file1" => $dataFile1,
"file2" => $dataFile2,
"file3" => $dataFile3,
];
TestUpload::updateOrCreate(['id' => $request->data['id'] ?? null], $obj);
...
}
public function deleteTestUpload()
{
...
Storage::disk('local')->delete('public/' . TestUpload::locationFile() . '/' . request()->data['file1']);
...
}
}
Hasilnya

Contoh Mengarsipkan File
Penggunaan contoh ini ada di menu 'Test Upload' bagian (kotak merah).
Contoh Tampilan Formulir

Terdapat 2 file yang harus ditambahkan kode untuk mengupload yaitu file .vue
dan .php
.
Contoh penggunaan ini menggunakan tabel test_upload
dengan code di file Upload.vue
dan UploadController.php
.
Direktori File
[project_folder]/
|-- app/
| `-- Http/
| `-- Controllers/
| `-- Admin/
| `-- UploadController.php
`-- resources/
`-- js/
`-- Pages/
`-- TestUpload/
`-- Upload.vue
File Upload.vue
<template>
...
<Button
label="Jadikan Satu File Zip"
severity="info"
@click="methods.fileupload.zip" />
...
</template>
<script>
import { reactive, onMounted, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { Upload} from '@/utils';
export default {
...
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
const methods = {
fileupload: {
zip:() => {
router.post(route("zippedFiles"),
{
data_file: [file, file, file],
}
, {
onSuccess: () => {
globalVariable.$toast.add({ severity: 'success',
summary: 'SUKSES',
detail: 'Berhasil Jadikan Zip',
life: 3000 });
},
onError: (error) => {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: error[Object.keys(error)[0]],
life: 3000 });
return;
}
});
}
},
}
};
return {
methods,
};
}
};
</script>
Beberapa kode yang perlu diperhatikan :
-
data_file: ...
-
Parameter tersebut berisikan data array yang telah di upload.
File UploadController.php
-
use wapmorgan\UnifiedArchive\UnifiedArchive;
-
Menggunakan library
UnifiedArchive
untuk mengarsipkan file. -
$object = [];
-
Variabel ini digunakan untuk menampung file-file yang akan diarsipkan.
-
foreach ($request->data_file ...
-
Kode ini untuk mengulang data permintaan file-file.
-
UnifiedArchive::create(...
-
Fungsi tersebut dari dokumentasi
UnifiedArchive
untuk mengarsipkan file yang memiliki 2 parameter$object
dan direktori arsip file dengan penamaan.
use wapmorgan\UnifiedArchive\UnifiedArchive;
class UploadController extends Controller
{
...
public function zippedFiles(Request $request)
{
if (!empty($request->data_file)) {
$object = [];
foreach ($request->data_file as $item) {
$object[$item['file1']] = Storage::disk('public')->path(TestUpload::locationFile().'/'.$item['file1']);
}
UnifiedArchive::create($object, Storage::disk('public')->path(TestUpload::locationFile() . '/'.time().'-archive.zip'));
...
}
}
}
PERHATIAN :
Jika ingin penggunaan arsip dengan ekstensi.rar
maka download terlebih dahulu ekstensi PHP dari PECL yaitu php_rar dan daftarkan kephp.ini
.
Adapun juga bisa melihat format apa saja yang bisa digunakan dan format yang belum terinstal di UnifiedArchive.
Contoh Kirim Email
Penggunaan contoh ini ada di menu 'Test Email'.
Tampilan Halaman

Cara Kirim Email
File yang digunakan untuk contoh yaituTestEmailController.php
dan template.blade.php
.
-
TestEmailController.php
-
Controller untuk contoh penggunaan kirim email.
-
template.blade.php
-
Contoh template yang digunakan untuk kirim email dan template ini memakai dari ckissi.
Direktori File yang dicontohkan
[project_folder]/
`-- app/
| `-- Http/
| `-- Controllers/
| `-- Admin/
| `-- TestEmailController.php
`-- resources/
`-- views/
`-- email/
`-- template.blade.php
TestEmailController.php
Beberapa kode yang perlu diperhatikan :-
$files = ...
-
Variabel ini digunakan untuk mengambil file untuk melampirkan file.
-
Mail::send(...
-
Laravel menyediakan API email yang bersih dan sederhana yang didukung oleh komponen Symfony Mailer yang populer.
-
..."email.template"...
-
Kode ini digunakan untuk mengambil template email dari
/resources/views/email/template.blade.php
. -
$message->to(...
-
Kode ini digunakan untuk mengirim email yang dituju.
-
->subject(...
-
Kode ini digunakan untuk 'Judul Email'.
-
$message->attach(...
-
Kode ini digunakan untuk melampirkan file ke email.
use Illuminate\Support\Facades\Mail;
class TestEmailController extends Controller
{
...
public function testSendEmail()
{
$mailData = [
'to' => 'to_your_email@gmail.com',
'title' => 'Test Email',
];
$files = TestUpload::where("type","Single in Multiple Data")->get()->map(function ($value) {
return Storage::disk('public')->path(TestUpload::locationFile().'/'.$value['file1']);
});
Mail::send("email.template", $mailData, function($message)use($mailData, $files) {
$message->to($mailData["to"])
->subject($mailData["title"]);
foreach ($files as $file){
$message->attach($file);
}
});
}
}
template.blade.php
Beberapa kode yang perlu diperhatikan :-
{{ $title }}
-
Variabel ini diambil dari Controller variabel
$mailData
sebelumnya.
<!DOCTYPE html>
<html lang="en">
<head>
...
</head>
<body>
...
{{ $title }}
...
</body>
</html>
Contoh State Management
Sebuah desain dalam coding di mana kita dapat memisahkan antara logic dan view, Tujuannya adalah agar logic dapat kembali digunakan (Source).
Kesimpulannya mengirim data 1 halaman ke halaman lain, state management ini menggunakan library Pinia.Penggunaan contoh ini ada di menu 'Test State Management'.
Tampilan Halaman

File contoh yang digunakan
Terdapat 3 file yang digunakan yaituStateManagement.vue
, DetailStateManagement.vue
dan perhitungan.ts
-
StateManagement.vue
-
File ini digunakan untuk menampilkan data utama.
-
DetailStateManagement.vue
-
File ini digunakan untuk menampilkan data yang telah dikirm.
-
perhitungan.ts
-
File ini digunakan sebagai penggunaan state management Pinia.
Direktori File yang akan dicontohkan
[project_folder]/
`-- resources/
`-- js/
`-- Pages/
`-- Admin/
`-- TestStateManagement/
|-- stores/
| `-- perhitungan.ts
|-- DetailStateManagement.vue
`-- StateManagement.vue
perhitungan.ts
Beberapa kode yang perlu diperhatikan yaitu :-
export const usePerhitunganStore ...
-
Kode ini digunakan nanti di file
.vue
untuk deklarasi class. -
defineStore(...
-
Sebelum mendalami konsep inti, kita perlu mengetahui bahwa penyimpanan didefinisikan menggunakan
defineStore()
dan memerlukan nama unik. -
const dataPerhitungan = ref([]);
-
Deklarasi variabel yang digunakan untuk menampung data tersebut.
-
function replacedPerhitungan(...
-
Fungsi ini digunakan untuk mengganti data variabel yang telah dideklarasi.
-
return { dataPerhitungan,replacedPerhitungan }
-
Kode ini digunakan untuk mengembalikan nilai yang telah didaftarkan.
import { ref, computed,toRaw } from 'vue';
import { defineStore } from "pinia";
export const usePerhitunganStore = defineStore('perhitungan', () => {
const dataPerhitungan = ref([]);
function replacedPerhitungan(data: never[]) {
if (data != undefined) {
dataPerhitungan.value.push(...data);
}
else{
dataPerhitungan.value = [];
}
}
return { dataPerhitungan,replacedPerhitungan }
})
StateManagement.vue
<template>
<div class="grid">
<div class="col-12">
<div class="card">
<div class="doc-intro">
<h4>Test State Management</h4>
</div>
<div v-for="row of data" :key="row.id" class="flex gap-1">
<Checkbox
v-model="selectedData"
:inputId="row.id"
name="category"
class="mb-2"
:value="row" />
<label :for="row.id">{{ row.angka1 }} + {{ row.angka2 }}</label>
</div>
<Button
label="Cek Hasil"
@click="methods.click.send"
severity="success"
class="mt-4"/>
</div>
</div>
</div>
<Toast />
</template>
<script>
import AppLayout from '@/Layouts/Admin/AppLayout.vue';
import { ref, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { usePerhitunganStore } from "./stores/perhitungan";
export default {
layout: AppLayout,
setup(props) {
const { replacedPerhitungan } = usePerhitunganStore();
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
const methods = {
click: {
send: () => {
if (selectedData.value.length == 0) {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: 'Perhitungan belum dipilih',
life: 3000 });
return;
}
Inertia.get(route("testDetailStateManagement"), {}, {
preserveState: true,
onBefore: () => {
replacedPerhitungan(selectedData.value);
},
});
}
}
}
const data = ref([
{ id: "index-1", angka1: 9, angka2: 3, },
{ id: "index-2", angka1: 8, angka2: 7, },
{ id: "index-3", angka1: 23, angka2: 3, },
{ id: "index-4", angka1: 15, angka2: 2, },
{ id: "index-5", angka1: 12, angka2: 6, },
]);
const selectedData = ref([]);
return {
data,
selectedData,
methods,
};
}
};
</script>
-
const { replacedPerhitungan } = usePerhitunganStore();
-
Kegunaan variabel tersebut hasil
return
dariperhitungan.ts
. -
replacedPerhitungan(...
-
Memanggil fungsi dari
perhitungan.ts
. -
const data = ref(...
-
Variabel data ini digunakan untuk menampung data yang dicontohkan.
DetailStateManagement.vue
<template>
<div class="grid">
<div class="col-12">
<div class="card">
<div class="doc-intro">
<h4>Test Detail Data</h4>
</div>
<DataTable :value="data" showGridlines tableStyle="min-width: 50rem">
<template #empty>
<div class="text-center">Tidak ada data</div>
</template>
<Column style="text-align: center;">
<template #header> <span class="flex-1 text-center">Angka 1</span> </template>
<template #body="{ data, frozenRow, index }">
<span>{{ data.angka1 }}</span>
</template>
</Column>
<Column>
<template #header> <span class="flex-1 text-center">Angka 2</span> </template>
<template #body="{ data, frozenRow, index }">
<div class="text-center">
<span>{{ data.angka2 }}</span>
</div>
</template>
</Column>
<Column>
<template #header> <span class="flex-1 text-center">Hasil</span> </template>
<template #body="{ data, frozenRow, index }">
<div class="text-center">
<span>{{ data.angka1 + data.angka2 }}</span>
</div>
</template>
</Column>
</DataTable>
<Button label="Kembali" @click="methods.click.back" severity="danger" icon="fa fa-reply" class="mt-4" />
</div>
</div>
</div>
<Toast />
</template>
<script>
import AppLayout from '@/Layouts/Admin/AppLayout.vue';
import { onMounted } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { usePerhitunganStore } from "./stores/perhitungan";
import { storeToRefs } from "pinia";
export default {
layout: AppLayout,
setup(props) {
const { dataPerhitungan } = storeToRefs(usePerhitunganStore());
const { replacedPerhitungan } = usePerhitunganStore();
const fn = {
back: () => {
Inertia.get(route("testStateManagement"), {}, {
preserveState: true,
onBefore: () => {
replacedPerhitungan();
},
});
}
}
onMounted(() => {
if (dataPerhitungan.value.length == 0) {
fn.back();
}
});
const data = dataPerhitungan.value;
const methods = {
click: {
back: () => {
fn.back();
}
}
};
return {
methods,
data,
};
}
};
</script>
-
Berguna untuk menampilkan data file yang telah dipilih dalam bentuk tabel.
-
const { dataPerhitungan } ...
-
Contoh pengisian dari Hak Akses Tambahan yaitu
xhrStoreMenu
ketika penambahan->middleware('permission:xhrStoreMenu');
. -
onMounted(() => { ...
-
Fungsi ini digunakan untuk jika
dataPerhitungan
kosong maka kembali ke halaman sebelumnya. -
const data = dataPerhitungan...
-
Variabel ini digunakan untuk menampilkan data yang telah dipilih.
Hasilnya

Contoh Realtime Data
Penggunaan contoh ini ada di menu 'Test Realtime'.
Tampilan Halaman

Realtime ini menggunakan Laravel Echo, Redis (Sebaiknya diinstal terlebih dahulu) dan Socket IO.
PRASYARAT
Beberapa file utama yang perlu diperhatikan yaitu.env
dan bootstrap.js
.
Direktori File yang perlu diperhatikan
[project_folder]/
|-- resources/
| `-- js/
| `-- bootstrap.js
`-- .env
.env
Beberapa kode yang perlu diperhatikan yaitu :-
BROADCAST_DRIVER
-
Driver ini diganti menggunakan
redis
untuk penggunaannya. -
LARAVEL_ECHO_PORT
-
Port ini digunakan saat running Laravel Echo.
...
BROADCAST_DRIVER=redis
...
LARAVEL_ECHO_PORT=6001
PERHATIAN :
Jangan terlupa ketika mengganti file.env
maka jalankan perintah di command promptphp artisan config:cache
.
bootstrap.js
// aktifkan socket
import Echo from 'laravel-echo';
window.Echo = new Echo({
broadcaster: 'socket.io',
host: window.location.hostname + ":" + window.laravel_echo_port
});
File yang dicontohkan
Beberapa file yang dicontohkan dari menu 'Test Realtime' yaitu :-
SendChat.php
-
File Events ini digunakan untuk mengatur channel dan untuk mengirim pesan.
-
Realtime.vue
-
File ini digunakan untuk menampilkan bagaimana realtime tersebut terjadi.
Direktori File yang dicontohkan
[project_folder]/
|-- app/
| `-- Events/
| `-- SendChat.php
`-- resources/
`-- js/
`-- Pages/
`-- TestRealtime/
`-- Realtime.vue
SendChat.php
namespace App\Events;
...
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class SendChat implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $chat;
public $user;
public function __construct($user,$chat)
{
$this->chat = $chat;
$this->user = $user;
}
public function broadcastOn(): String
{
return new Channel('chat-channel');
}
public function broadcastAs(): String
{
return 'ChatEvent';
}
public function broadcastWith() : array
{
return [
'user'=>$this->user,
'chat'=>$this->chat,
];
}
}
-
__construct(...
-
Fungsi utama ini digunakan menerima 2 parameter yaitu
$user
sebagai menerima id dan$chat
sebegai menerima pesanan. -
new Channel('chat-channel');
-
Kode ini digunakan untuk set channel sebagai nama brodcast.
-
Fungsi ini digunakan untuk set alias name di
.vue
. -
broadcastWith()
-
Ini digunakan return data
$user
sebagai menerima id dan$chat
sebegai menerima pesanan.
Realtime.vue
<template>
<div class="grid">
<div class="col-12">
<div class="card">
<div class="doc-intro">
<h4>Test Realtime Chat</h4>
</div>
<div class="flex flex-wrap gap-4 justify-content-center">
<div class="flex align-items-center">
<RadioButton v-model="form.user" inputId="user1" name="user" value="1" />
<label for="user1" class="ml-2"> <i class="pi pi-user"></i> User 1</label>
</div>
<div class="flex align-items-center">
<RadioButton v-model="form.user" inputId="user2" name="user" value="2" />
<label for="user2" class="ml-2"><i class="pi pi-user"></i> User 2</label>
</div>
</div>
<div class="grid mt-4">
<div class="col-12" v-for="(row, index) of data.chatRoom" :key="index">
<div class="flex justify-content-end" v-if="row.user == form.user">
<div class="bg-primary text-white border-round p-2 w-fit">
{{ row.chat }}
</div>
</div>
<div class="bg-green-600 text-white border-round p-2 w-fit" v-else>
{{ row.chat }}
</div>
</div>
</div>
<form @submit.prevent="methods.submit.send">
<div class="formgrid grid">
<div class="field col-12">
<span class="p-input-icon-right w-full">
<i class="fa-regular fa-paper-plane"></i>
<InputText type="text" v-model="form.chat" placeholder="Kirim" class="w-full" />
</span>
</div>
</div>
</form>
</div>
</div>
</div>
<Toast />
</template>
<script>
import AppLayout from '@/Layouts/Admin/AppLayout.vue';
import { reactive, getCurrentInstance } from 'vue';
import { Inertia } from '@inertiajs/inertia';
import { usePage } from '@inertiajs/vue3';
export default {
layout: AppLayout,
setup(props) {
const globalVariable = getCurrentInstance().appContext.config.globalProperties;
const form = reactive({
user: null,
chat: null
});
const data = reactive({
chatRoom: []
});
window.Echo.channel('chat-channel')
.listen('.ChatEvent', (resultEcho) => {
data.chatRoom.push(resultEcho);
form.chat = null;
});
const methods = {
submit: {
send: () => {
if (form.user == null) {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: 'User belum dipilih',
life: 3000 });
return
}
if (form.chat == null || form.chat == '') {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: 'Chat belum diisi',
life: 3000 });
return
}
axios.post(route("sendChatRealtime"), {
form: form
})
.then((result) => {
})
.catch((error) => {
if (error.response.status == 422) {
globalVariable.$toast.add({ severity: 'error',
summary: 'PERHATIAN',
detail: Object.values(error.response.data.errors)[0][0],
life: 3000 });
}
else {
console.log(error);
}
});
}
}
}
return {
data,
form,
methods,
};
}
};
</script>
-
const data = ...
-
Variabel ini digunakan untuk menampung data chat.
-
window.Echo.channel('chat-channel'...
-
Kode ini digunakan untuk mengaktifkan Echo dari laravel-echo.
chat-channel
ini diisi daribroadcastOn()
. -
.listen('.ChatEvent...
-
ChatEvent
ini diisi daribroadcastAs()
.
PERHATIAN :
Jika ingin membuat Events sendiri maka jalankan perintahphp artisan make:event SendMessage
dalam contoh perintah tersebutSendMessage
.
Cara Running
Menjalankan redis untuk penyimpanan data chat.
redis-server
Menjalankan laravel-echo.
laravel-echo-server start
Maka akan menghasilkan :
L A R A V E L E C H O S E R V E R
version 1.6.3
⚠ Starting server in DEV mode...
✔ Running at localhost on port 6001
✔ Channels are ready.
✔ Listening for http events...
✔ Listening for redis events...
Server ready!
[2023-09-25T03:38:49.765Z] - qPTMKZypRO7gbk4wAAAA joined channel: chat-channel
[2023-09-25T03:39:19.522Z] - qPTMKZypRO7gbk4wAAAA left channel: chat-channel (transport close)
[2023-09-25T03:39:21.033Z] - QsGJm0g1EC_qAFvlAAAB joined channel: chat-channel
[2023-09-25T03:39:24.496Z] - QsGJm0g1EC_qAFvlAAAB left channel: chat-channel (transport close)
[2023-09-25T03:39:33.864Z] - xb4rvzEZZ1ZI7vPtAAAC joined channel: chat-channel
Channel: chat-channel
Event: ChatEvent
Hasilnya

Contoh Pembuatan Data CRUD
Penggunaan contoh ini ada di menu 'Upload Configurations'.
Beberapa file-file yang perlu diperhatikan dalam contoh ini ialah :
-
UploadController.php
-
File ini digunakan sebagai aksi beberapa fungsi.
-
Upload.vue
-
Sebagai tampilan halaman.
Direktori File yang dicontohkan
[project_folder]/
|-- app/
| `-- Http/
| `-- Controllers/
| `-- Admin/
| `-- UploadController.php
`-- resources/
`-- js/
`-- Pages/
`-- Admin/
`-- Upload/
`-- Upload.vue
UploadController.php
Beberapa kode yang perlu diperhatikan :-
...->paginate(10);
-
Kode ini digunakan untuk menampilkan data paginasi dari yang dicontohkan ialah
10
. -
return redirect()->route('upload');
-
Kode ini untuk menuju ke halaman tertentu sesuai route yang diinginkan.
public function index()
{
...
$uploads = ...->paginate(10);
...
'uploads' => $uploads,
...
}
public function store(Request $request)
{
...
return redirect()->route('upload');
...
}
Upload.vue
Beberapa kode yang perlu diperhatikan :-
(JSON.parse(JSON.stringify(params),...
-
Kode ini digunakan untuk menghapus
undefined
,null
dan''
(Source). -
const data = ...
-
Variabel ini digunakan untuk menampung data dari paginate() :
-
data: props.uploads.data,
-
Kode ini digunakan untuk menampilkan data.
-
currentPage: props.uploads.current_page,
-
Kode ini digunakan untuk mengetahui page ke berapa.
-
perPage: props.uploads.per_page,
-
Kode ini digunakan untuk mengetahui data yang tampil sebatas berapa.
-
total: props.uploads.total
-
Kode ini digunakan untuk mengetahui total data semua.
-
-
watch(() => props.uploads,...
-
Kode ini digunakan untuk melihat apakah ada perubahan dari variabel tertentu maka akan diubah datanya sesuai yang telah diatur.
...
const fn = {
reloadPageWithParameter: (params) => {
Inertia.get(route('upload'),
(JSON.parse(JSON.stringify(params),
(key, value) => value === null || value === '' ? undefined : value)
));
}
};
const data = reactive({
tables: {
data: props.uploads.data,
currentPage: props.uploads.current_page,
perPage: props.uploads.per_page,
total: props.uploads.total
}
});
watch(() => props.uploads,
(newVal) => {
data.tables = {
data: newVal.data,
currentPage: newVal.current_page,
perPage: newVal.per_page,
total: newVal.total
};
...
}
);
...
Routes
Contoh penggunaan Routes / URL dari vue ke routes.php
.
Beberapa file-file yang perlu diperhatikan dalam contoh ini ialah :
-
web.php
-
File ini digunakan untuk Routing API.
-
Menu.vue
-
Sebagai contoh file yang digunakan untuk simpan data dan refresh data.
-
StateManagement.vue
-
Sebagai contoh file halaman awal yang akan menuju ke file
DetailStateManagement.vue
. -
DetailStateManagement.vue
-
Sebagai contoh file halaman yang akan kembali menuju ke file
StateManagement.vue
.
Direktori File yang dicontohkan
[project_folder]/
|-- routes/
| `-- web.php
`-- resources/
`-- js/
`-- Pages/
`-- Admin/
|-- Menus/
| `-- Menu.vue
`-- TestStateManagement/
|-- DetailStateManagement.vue
`-- StateManagement.vue
web.php
Beberapa kode yang perlu diperhatikan :-
Route::get('/',...
-
Kode ini untuk redirect ke fungsi
index
. -
Route::get('/storeMenu',...
-
Kode ini untuk redirect ke fungsi
store
.
...
Route::prefix('menu')->group(function () {
Route::get('/', [\App\Http\Controllers\Admin\MenuController::class,'index'])->name('menu');
...
Route::post('/storeMenu', [\App\Http\Controllers\Admin\MenuController::class,'store']);
});
...
Menu.vue
Jadi halaman menu ini ada di URLhttp://localhost:8000/admin/menu
.
Beberapa kode yang perlu diperhatikan :
-
'./menu'
-
Contoh halaman yang digunakan ialah Menu jadi untuk halaman
get
awal dalam url yang digunakan di.vue
maka dari prefix nama diweb.php
. -
'./menu/storeMenu'
-
Jadi jika ingin mengakses
Route::post('/storeMenu',...
diweb.php
yaitu menyimpan data menu maka url tersebut./[halaman utama]/[url yang diinginkan]
.
...
layout: () => {
Inertia.get('./menu', {
layout: form.layout
}, {
preserveState: true,
});
}
...
save: () => {
...
router.post('./menu/storeMenu', {
data: arrayMenu
}, {
onSuccess: (result) => {
globalVariable.$toast.add({
severity: 'success',
summary: 'SUKSES',
detail: 'Berhasil Simpan Menu',
life: 3000
});
},
onError: (error) => console.log(error)
});
}
web.php
Beberapa kode yang perlu diperhatikan :-
Route::get('/',...
-
Kode ini untuk redirect ke fungsi
index
. -
Route::get('/detail',...
-
Kode ini untuk redirect ke halaman
detail
.
...
Route::prefix('testStateManagement')->group(function () {
Route::get('/', [\App\Http\Controllers\Admin\TestStateManagementController::class,'index'])->name('testStateManagement');
Route::get('/detail', [\App\Http\Controllers\Admin\TestStateManagementController::class,'detail']);
});
...
...
send: () => {
...
Inertia.get("./testStateManagement/detail", {}, {
preserveState: true,
onBefore: () => {
replacedPerhitungan(selectedData.value);
},
});
}
...
...
back: () => {
Inertia.get("./", {}, {
preserveState: true,
onBefore: () => {
replacedPerhitungan();
},
});
}
...
-
"./testStateManagement/detail"
-
Jadi jika ingin mengakses
DetailStateManagement.vue
maka url tersebut./[halaman utama]/[url yang diinginkan]
. -
"./"
-
Jika ingin kembali ke halaman sebelumnya jangan memakai
window.history.back()
maka cukup panggil url"./"
.
PERHATIAN :
Jadi URL aksi mengacu pada URI terakhir dalam contoh yang sudah ada sepertihttp://localhost:8000/admin/menu
maka URL/halaman aktif ialahmenu
jadi jika ingin memanggil URL untuk simpan data, menghapus data dan mengubah data maka URL tersebut./menu/[URL aksi]
dan jika ingin halaman ke sebelumnya seperti contohhttp://localhost:8000/admin/testStateManagement/detail
ke halamantestStateManagement
maka cukup./
.
Kekurangan
Boilerplate ini mempunyai kekurangan tetapi dalam kekurangan tersebut ada cara mengatasi dan alternatif.
Dynamic Dialog
Kekurangan dari Dynamic Dialog dalam dokumentasi halaman resminya cara penggunaannya yaitu membuat 2 file body
untuk isi dari dialog-nya dan footer
sebagai catatan kaki dialog tersebut.
Hal ini sudah ada yang mengajukan pertanyaan tetapi masih belum ada jawaban dari pihak resmi terkait (Source).
Cara Mengatasi :
Contoh file yang digunakan dari cara mengatasi ini yaitu :
-
Menu.vue
-
File ini digunakan untuk memanggil dialog tersebut.
-
ModalFormMenu.vue
-
File ini digunakan sebagai tempat dari kode dialog tersebut.
Direktori File
[project_folder]/
`-- resources/
`-- js/
`-- Pages/
`-- Admin/
`-- Menus/
|-- Menu.vue
`-- ModalFormMenu.vue
Menu.vue
Beberapa kode yang perlu diperhatikan yaitu :-
showHeader: ...
-
Kode tersebut digunakan untuk apakah dialog tersebut ingin menampilkan
header
.Dalam cara mengatasi tersebut diisi
false
yang artinyaheader
tidak ditampilkan. -
contentClass: ...
-
Tambahan kelas dalam dialog tersebut.
Dalam cara mengatasi tersebut diisi
p-dialog
yang ingin memakai class dari yang diberikan PrimeVue untuk set ulang dialog. -
contentStyle: ...
-
Tambahan style yang diinginkan dalam dialog tersebut.
Dalam cara mengatasi tersebut diisi :
-
padding:0 !important;
-
Untuk menghilangkan jarak dalam dialog tersebut.
-
max-height:100%;
-
Memberikan maksimal ketinggian 100%.
-
overflow-y:unset;
-
Menghilangkan scroll vertikal.
-
globalVariable.$dialog.open(ModalFormMenu, {
props: {
showHeader: false,
style: {
width: '50vw',
},
breakpoints: {
'960px': '75vw',
'640px': '90vw'
},
modal: true,
contentClass: 'p-dialog',
contentStyle: 'padding:0 !important;max-height:100%;overflow-y:unset;'
},
data: {
statusModal: statusModal,
layout: form.layout,
dataModal: dataModal
},
onClose: (options) => {
const dataResultModal = options.data;
if (dataResultModal) {
data.tree.push(dataResultModal);
}
}
});
ModalFormMenu.vue
Beberapa kode yang perlu diperhatikan yaitu :-
this.thisInject.close()
-
Kode ini digunakan untuk memanggil fungsi
onClose
dari fileMenu.vue
. -
style="overflow: auto;max-height: 412px;"
-
Style ini digunakan untuk mengaktifkan scroll dan memberikan maksimal ketinggian default dialog 412px.
-
const thisInject = inject('dialogRef');
-
Kode tersebut digunakan untuk mengambil data dari dialog yang dikirimkan dari file
Menu.vue
<template>
<div class="p-dialog-header">
<span class="p-dialog-title"><!-- Judul Dialog --></span>
<div class="p-dialog-header-icons">
<button
class="p-dialog-header-icon p-dialog-header-close p-link"
type="button"
@click="this.thisInject.close()">
<i class="pi pi-times"></i>
<span class="p-ink"></span>
</button>
</div>
</div>
<div class="p-dialog-content" style="overflow: auto;max-height: 412px;">
<!-- Isi Dialog -->
</div>
<div class="p-dialog-footer">
<!-- Isi Footer Dialog -->
</div>
</template>
<script>
export default {
setup(props) {
const thisInject = inject('dialogRef');
return {
thisInject,
}
}
};
</script>
Adapun fitur dialog yang diperbaiki juga yaitu Maximizable.
File-file yang digunakan dalam perbaikan fitur ini yaitu :
-
ModalFormMenu.vue
-
File ini digunakan untuk memanggil dialog tersebut.
-
ModalPilihIcon.vue
-
File ini digunakan sebagai implementasi fitur Maximizable.
Direktori File
[project_folder]/
`-- resources/
`-- js/
`-- Pages/
`-- Admin/
`-- Menus/
|-- ModalFormMenu.vue
`-- ModalPilihIcon.vue
ModalFormMenu.vue
Beberapa kode yang perlu diperhatikan yaitu :-
showHeader: ...
-
Kode tersebut digunakan untuk apakah dialog tersebut ingin menampilkan
header
.Dalam cara mengatasi tersebut diisi
false
yang artinyaheader
tidak ditampilkan. -
contentClass: ...
-
Tambahan kelas dalam dialog tersebut.
Dalam cara mengatasi tersebut diisi
p-dialog
yang ingin memakai class dari yang diberikan PrimeVue untuk set ulang dialog. -
contentStyle: ...
-
Tambahan style yang diinginkan dalam dialog tersebut.
Dalam cara mengatasi tersebut diisi :
-
padding:0 !important;
-
Untuk menghilangkan jarak dalam dialog tersebut.
-
max-height:100%;
-
Memberikan maksimal ketinggian 100%.
-
overflow-y:unset;
-
Menghilangkan scroll vertikal.
-
-
Menghilangkan Breakpoints
-
Perbedaan dalam memperbaiki fitur ini Breakpoints dihilangkan karena nanti akan berpengaruh ketika dialog dikecilkan seukuran smartphone.
globalVariable.$dialog.open(ModalFormMenu, {
props: {
showHeader: false,
style: {
width: '50vw',
},
modal: true,
contentClass: 'p-dialog',
contentStyle: 'padding:0 !important;max-height:100%;overflow-y:unset;'
},
data: {
statusModal: statusModal,
layout: form.layout,
dataModal: dataModal
},
onClose: (options) => {
const dataResultModal = options.data;
if (dataResultModal) {
data.tree.push(dataResultModal);
}
}
});
ModalPilihIcon.vue
<template>
<div class="p-dialog-header">
<span class="p-dialog-title"><!-- Judul Dialog --></span>
<div class="p-dialog-header-icons">
<button
class="p-dialog-header-icon p-dialog-header-close p-link"
type="button"
@click="methods.click.maximizedMinimized">
<i :class="{
'pi pi-window-minimize': status.icon.maximizedMinimized,
'pi pi-window-maximize': !status.icon.maximizedMinimized
}"></i>
<span class="p-ink"></span>
</button>
<button
class="p-dialog-header-icon p-dialog-header-close p-link"
type="button"
@click="this.thisInject.close()">
<i class="pi pi-times"></i>
<span class="p-ink"></span>
</button>
</div>
</div>
<div class="p-dialog-content" style="overflow: auto;max-height: 412px;">
<!-- Isi Dialog -->
</div>
<div class="p-dialog-footer">
<!-- Isi Footer Dialog -->
</div>
</template>
<script>
export default {
setup(props) {
const thisInject = inject('dialogRef');
const status = reactive({
icon: {
maximizedMinimized: false,
}
});
const methods = {
click: {
maximizedMinimized: () => {
if (document.getElementsByClassName('p-dialog p-component')
[document.getElementsByClassName('p-dialog p-component').length - 1]
.classList.contains("p-dialog-maximized")) {
document.getElementsByClassName('p-dialog p-component')
[document.getElementsByClassName('p-dialog p-component').length - 1]
.classList.remove("p-dialog-maximized");
status.icon.maximizedMinimized = false;
}
else {
status.icon.maximizedMinimized = true;
document.getElementsByClassName('p-dialog p-component')
[document.getElementsByClassName('p-dialog p-component').length - 1]
.classList.add("p-dialog-maximized");
}
}
}
}
return {
thisInject,
status,
methods,
}
}
};
</script>
-
this.thisInject.close()
-
Kode ini digunakan untuk memanggil fungsi
onClose
dari fileMenu.vue
. -
methods.click.maximizedMinimized
-
Fungsi ini digunakan untuk mengaktifkan Maximizable.
-
const thisInject = inject('dialogRef');
-
Kode tersebut digunakan untuk mengambil data dari dialog yang dikirimkan dari file
Menu.vue
-
maximizedMinimized: () => ...
-
Fungsi ini digunakan untuk mengaktifkan Maximizable.
Alternatif : (belum ada)
Button:Button Set
Kekurangan dari Button bagian Button Set ini ketika button didalam tersebut hanya 1 dan ketika ada penggunaan tag a href
.
Hasil Penggunaan Default

Ketika hanya ada 1 button.
Hasil

Ketika dikombinasikan dengan tag element a href
.
Hasil

Kenapa memakai tag element a href
, kode element ini digunakan untuk button download file yang mengharuskan memakai a href
.
Cara Mengatasi :
File yang dicoba untuk mengatasi bug ini ada di Upload.vue
.
Direktori File
[project_folder]/
`-- resources/
`-- js/
`-- Layouts/
`-- TestUpload/
`-- Upload.vue
Upload.vue
.button-set :deep(.p-button:first-child:not(:only-child)) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.button-set :deep(.p-button:last-child:not(:only-child)) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.button-set :deep(.p-button:not(:first-child):not(:last-child)) {
border-radius: 0 !important;
}
.button-set a:not(:first-child):not(:last-child) .p-button {
border-radius: 0 !important;
}
.button-set a:first-child:not(:only-child) .p-button {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.button-set a:last-child:not(:only-child) .p-button {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
Penggunaan class tersebut cukup menggunakan tag element span
lalu bisa diberikan class button-set
.
<span class="button-set">
<a href="#"><Button label="Delete" severity="warning" icon="pi pi-trash" /></a>
<Button label="Save" icon="pi pi-check" />
</span>
Alternatif : (belum ada)
File Upload
Kekurangan dari File Upload ini tidak ada props untuk disable pesan invalid dari salah ekstensi atau pembatasan terhadap ukuran.
Bug tersebut muncul ketika penggunaan 'Upload Single Input' dan juga untuk saat ini jika ada salah ekstensi atau file lebih dari batasan yang ditentukan akan muncul alert atau toast.
Bukti

Dalam Bukti tersebut tampil Message yang tidak tepat.
Cara Mengatasi :
File yang dicoba untuk mengatasi bug ini ada di Upload.vue
.
Direktori File
[project_folder]/
`-- resources/
`-- js/
`-- Layouts/
`-- TestUpload/
`-- Upload.vue
Upload.vue
.p-fileupload :deep(.p-message) {
display: none;
}
Alternatif : (belum ada)
Cara Mengatasi :
Beberapa kode yang perlu diperhatikan yaitu :-
<p>...</p>
-
Menambahkan tag <p></p> sebagai penampil nama gambar.
-
display:table-cell;
-
Menambahkan style display:table-cell untuk membungkus teks.
-
vertical-align:middle;
-
Menambahkan style vertical-align:middle; untuk diposisikan ditengah-tengah kolom.
<template>
<div class="card flex justify-content-center">
<Image alt="Image" preview>
<template #indicatoricon>
<i class="pi pi-search"></i>
</template>
<template #image>
<p style="display: table-cell;vertical-align: middle;">
galleria-galleria-galleria-galleria11.jpg
</p>
</template>
<template #preview="slotProps">
<img src="galleria-galleria-galleria-galleria11.jpg"
alt="preview"
:style="slotProps.style"
@click="slotProps.onClick" />
</template>
</Image>
</div>
</template>
Alternatif : (belum ada)
Update
Ada 2 hal yang bisa update untuk saat ini.
Package.json
Buka Command Prompt di dalam direktori folder Boilerplate tersebut
Masukkan perintah :npm i -g npm-check-updates
ncu -u
npm install
Composer.json
Buka Command Prompt di dalam direktori folder Boilerplate tersebut
Masukkan perintah :composer update
History
Informasi terkait update apakah ada error atau tidaknya.
Tanggal Update :
-
19-11-2023
-
-
Implementasi Proyek 1 ✅
-
Tidak ada error
-
Implementasi Proyek 2 ✅
-
Tidak ada error
-
-
09-01-2024
-
-
Error
-
Trailing Slash adalah garis miring ke depan ("/") yang ditempatkan di akhir URL seperti domain.com/ atau domain.com/page/. Hal ini membuat error di project untuk cara perbaikannya lihat dilink berikut.
-
Implementasi Proyek 1 ❌
-