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
  • Bahasa Pemrograman

  • Laravel ini menggunakan bahasa pemrograman PHP dengan versi yang didukung 8.1 s/d 8.2.

  • Composer

  • Laravel ini membutuhkan Composer 2.2.0 atau lebih tinggi.

  • Database

  • 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

Instalasi

1. Buka Command Prompt di dalam direktori folder Boilerplate tersebut
Masukkan perintah :
npm install
Perintah tersebut berguna untuk menginstall berbagai modul node.js yang ada di file package.json.
Masukkan perintah berikutnya:
composer install
Perintah tersebut berguna untuk menginstal library yang dibutuhkan sesuai yang ada di file composer.json.
2. Membuat tautan penyimpanan
Buka Command Prompt di dalam direktori folder Boilerplate tersebut.
Masukkan perintah :
php artisan storage:link
Perintah untuk membuat tautan simbolis antara direktori publik.
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=
...
Di dalam file tersebut terdapat code 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
Perintah untuk menghasilkan string acak yang digunakan sebagai key yang diperlukan untuk semua proses enkripsi dan dekripsi pada boilerplate.
Masukkan perintah berikutnya:
php artisan config:cache
Perintah dalam Laravel yang digunakan untuk membuat cache semua file konfigurasi aplikasi dalam satu file untuk meningkatkan kinerja aplikasi dengan mengurangi jumlah akses ke sistem file dan membuat aplikasi berjalan lebih cepat. Lalu lanjutkan menjalankan 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
Maka akan menghasilkan :
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 dan Public, 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 dan Public, 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
Perintah yang digunakan untuk memulai script dalam pengembangan dengan Vite yang merupakan alat pengembangan web cepat untuk proyek-proyek front-end.
Masukkan perintah berikutnya:
php artisan serve
Perintah 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 ganti APP_URL yang ada di file .env.
contoh : menggunakan port 1234 ganti code APP_URL=http://localhost:1234 maka ketika menjalankan perintah di command prompt php artisan serve --port=1234 dan jangan lupa sebelum menjalankan perintah serve tersebut jalankan perintah php 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 :

  • $toast

  • Variabel ini digunakan untuk memberikan notifikasi di halaman website.

    Contoh ketika simpan menu maka akan tampil 'Berhasil Simpan' dibagian ujung atas kanan.

  • $confirm

  • Variabel ini digunakan untuk menampilkan Dialog atau Pop up yang bersifat konfirmasi.

    Contoh ketika tampil ada pertanyaan 'Apakah anda yakin ingin menghapus data ini?'.

  • $dialog

  • 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 :

en.json
{
  "languages": {
    "id": "Indonesia",
    "en": "English"
  },
  "title": {
    "config": "English"
  }
}
id.json
{
  "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
AppHeader.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>
                              
Beranda.vue
<template>
    <h1>{{ $t('title.config') }}</h1>
</template>
                              

Hasilnya

menu

Tema Public

Public mempunyai 2 tema yaitu Normal dan Landing.

Normal

menu

Landing

menu

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.

Beranda.vue : Normal
<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>
Beranda.vue : Landing
<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.

Membuat Data Menu

Prasyarat

Sebelum tambah data menu tambah rute menu di web.php, disini menggunakan contoh rute menu.

  • Route::prefix('admin') ...

  • Terdapat 3 layout prefix group yang ada di web.php yaitu :

    1. public
    2. autentikasi
    3. admin

    Contoh penambahan menu ada di layout admin.

  • Route::prefix('menu')->group(function () { ...

  • Pengisian prefix 'menu' akan dipakai untuk formulir Route Name.

  • Route::get('/', ...)->name('menu')

  • Untuk url yang digunakan '/' menu utama yang tampil di sidebar menu atau akses awal menu. Pengisian name 'menu' akan dipakai untuk formulir Route Name.

Route::prefix('admin')->group(function () {
    Route::prefix('menu')->group(function () {
        Route::get('/', [\App\Http\Controllers\Admin\MenuController::class,'index'])->name('menu');
    });
});

PERHATIAN :
ALASAN :
Prefix dan name rute get menu harus sama dikarenakan akan mempengaruhi active menu class CSS jika didalam menu tersebut ada halaman detail.

1. Pilih menu yang bertuliskan 'Menu'
Keterangan yang ada di halaman :
  1. Pilihan Layout
    • Admin : Layout admin ini yang saat ini tampil.
    • Public : Layout menu ini saat sebelum login.
  2. Terdapat 2 Button di luar data
    • Tambah : Untuk tambah data baru.
    • Simpan Menu : Untuk menyimpan menu yang telah diatur.
  3. Terdapat 3 Button di dalam data
    • Warna biru icon tambah : Untuk tambah submenu baru dari data menu yang dipilih.
    • Warna kuning icon pensil : Untuk mengubah menu.
    • Warna merah icon tong sampah : Untuk menghapus menu.

Halaman Menu

menu
2. Klik tombol 'Tambah'
Keterangan yang ada diformulir 'Tambah' :
  • Icon

  • Icon menu dapat memilih dengan cara:

    1. Klik tombol Pilih Icon.
    2. tampil Dialog berisi icon-icon.
    3. Pilih icon yang ingin ditampilkan.
  • Label

  • Berguna untuk menampilkan nama menu.

  • Route Name

  • Berguna untuk menuju rute link dari alias name yang sudah didaftarkan file web.php.

    Route Name bisa dikosongkan karena jika menu mempunyai submenu maka rute link tidak aktif.

  • Function

  • Berguna untuk jika menu tidak digunakan sebagai menampilkan halaman tetapi digunakan untuk hal lain contoh download file, menampilkan dialog, dll.

    Jika Route Name dan Function ini diisi maka Route Name tidak bisa digunakan. Untuk pengisian bisa langsung isi nama fungsi contoh test();. Fungsi tersebut akan mengisi di file :

    1. Layout Admin :
      • /resources/js/Layouts/Admin/AppSidebar.vue

    2. Layout Public (Normal) :
      • /resources/js/Layouts/Public/AppTopbar.vue

    3. Layout Public (Landing) :
      • /resources/js/Layouts/Landing/AppHeader.vue

  • Apakah Perlu Hak Akses?

  • Terdapat 2 pilihan yang bisa dipilih salah satu yaitu :

    1. Ya :
      Jika memilih tersebut maka data menu ini akan tampil di menu Role untuk mengatur menu yang tampil di Role apa saja.
    2. Tidak :
      Jika memilih tersebut maka data menu ini tidak tampil di menu Role.
  • Jenis Config

  • Secara default formulir input ini disabled akan bisa dipilih ketika memilih formulir input sebelumnya.

    Terdapat 3 config yaitu :

    1. Sebelum Login :
      Config ini tampil ketika memilih formulir input sebelumnya Tidak. Menu akan tampil ketika sebelum login jika setelah login menu akan tidak tampil.
    2. Selalu Tampil :
      Config ini tampil ketika memilih formulir input sebelumnya Tidak. Menu akan selalu tampil meskipun belum login atau sesudah login.
    3. Setelah Login :
      Config ini tampil dan terpilih langsung ketika memilih formulir input sebelumnya Ya. Menu akan tampil setelah login dengan sesuai Role yang telah diatur.

Formulir Tambah

menu

PERHATIAN :
Rekomendasi pengisian Route Name tersebut menggunakan Camel Case. Contoh masterPegawai, masterKota, masterStatus.

Membuat Data Role

Tampilan Halaman Role

menu

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 :

    1. Checkbox :
      Jika dipilih maka menu ini akan tampil sesuai role yang diatur.
    2. Formulir input 'Isi Hak Akses' :
      Pengisian ini digunakan untuk hak akses saat tampilnya menu tersebut. Contoh bisaMenambah, bisaMengubah, bisaMenghapus, dll.

Formulir Tambah

menu

PERHATIAN :
Rekomendasi pengisian Hak Akses tersebut menggunakan Camel Case. Contoh bisaMenambah, 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 dan xhrStoreMenu 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

menu
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 :

    1. A
    2. B
  • Penjual

  • Memiliki menu yang dipilih :

    1. C
    2. D
  • Jika dipilih 2 roles tersebut

  • Menu yang tampil di user tersebut :

    1. A
    2. B
    3. C
    4. D

Formulir Tambah

menu
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

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

menu

Contoh Upload Single Basic

Penggunaan contoh ini ada di menu 'Test Upload'.

Contoh Tampilan Formulir

menu

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 :

  • FileUpload

  • Penggunaan component ini dari PrimeVue.

    • mode="basic"

    • Mendefinisikan UI komponen, value yang bisa digunakan adalah advanced dan basic.

    • 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 dari init: new Upload(id_upload) dan information 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, dan id_upload.

  • $dataUploaded = ...->uploads(...

  • Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request file dan lokasi 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

menu

Contoh Upload Single Input

Penggunaan contoh ini ada di menu 'Test Upload'.

Contoh Tampilan Formulir

menu

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 :

  • InputText

  • 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.

  • FileUpload

  • Penggunaan component ini dari PrimeVue.

    • mode="basic"

    • Mendefinisikan UI komponen, value yang bisa digunakan adalah advanced dan basic.

    • 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 dari init: new Upload(id_upload) dan information 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, dan id_upload.

  • $dataUploaded = ...->uploads(...

  • Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request file dan lokasi 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

menu

Contoh Upload Single In Multiple Data (Table)

Penggunaan contoh ini ada di menu 'Test Upload'.

Contoh Tampilan Formulir

menu

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 :

  • FileUpload

  • Penggunaan component ini dari PrimeVue.

    • ref="refFileUploadSingleInMultipleData"

    • ref adalah atribut khusus, mirip dengan atribut key yang dibahas v-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 dan basic.

    • 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.

  • DataTable

  • Berguna untuk menampilkan data file yang telah dipilih dalam bentuk tabel.

  • const data = ...

  • Variabel data ini berguna untuk menampung data upload dari init: new Upload(id_upload) dan information 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, dan id_upload.

  • $dataUploaded = ...->uploads(...

  • Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request file dan lokasi 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

menu

PERHATIAN :
Jika penggunaan upload tersebut menggunakan multiple maka tidak perlu menggunakan index langsung event.files.

Contoh Upload Single Object

Penggunaan contoh ini ada di menu 'Test Upload'.

Contoh Tampilan Formulir

menu

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 dan file3_download untuk link download file tersebut.

  • getFile1DownloadAttribute(), getFile2DownloadAttribute(), getFile3DownloadAttribute()

  • Fungsi ini berguna untuk menggantikan isi default kolom file1_download, file2_download dan file3_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 :

  • InputText

  • 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.

  • FileUpload

  • Penggunaan component ini dari PrimeVue.

    • mode="basic"

    • Mendefinisikan UI komponen, value yang bisa digunakan adalah advanced dan basic.

    • 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 dari init: new Upload(id_upload) dan information 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, dan id_upload.

  • $dataUploaded = ...->uploads(...

  • Fungsi ini digunakan untuk upload file ke folder yang diinginkan dengan parameter request file dan lokasi 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

menu

Contoh Mengarsipkan File

Penggunaan contoh ini ada di menu 'Test Upload' bagian (kotak merah).

Contoh Tampilan Formulir

menu

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 ke php.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

menu
Cara Kirim Email
File yang digunakan untuk contoh yaitu TestEmailController.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

menu
File contoh yang digunakan
Terdapat 3 file yang digunakan yaitu StateManagement.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>
Beberapa kode yang perlu diperhatikan yaitu :
  • const { replacedPerhitungan } = usePerhitunganStore();

  • Kegunaan variabel tersebut hasil return dari perhitungan.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>
Beberapa kode yang perlu diperhatikan yaitu :
  • DataTable

  • 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

menu

Contoh Realtime Data

Penggunaan contoh ini ada di menu 'Test Realtime'.

Tampilan Halaman

menu

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 prompt php 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,
        ];
    }

}
Beberapa file yang perlu diperhatikan yaitu :
  • __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.

  • broadcastAs()

  • 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>
Beberapa file yang perlu diperhatikan :
  • 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 dari broadcastOn().

  • .listen('.ChatEvent...

  • ChatEvent ini diisi dari broadcastAs().

PERHATIAN :
Jika ingin membuat Events sendiri maka jalankan perintah php artisan make:event SendMessage dalam contoh perintah tersebut SendMessage.

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

menu

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 URL http://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 di web.php.

  • './menu/storeMenu'

  • Jadi jika ingin mengakses Route::post('/storeMenu',... di web.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']);
});
...
StateManagement.vue
...
send: () => {
    ...
    Inertia.get("./testStateManagement/detail", {}, {
        preserveState: true,
        onBefore: () => {
            replacedPerhitungan(selectedData.value);
        },
    });
}
...
DetailStateManagement.vue
...
back: () => {
    Inertia.get("./", {}, {
        preserveState: true,
        onBefore: () => {
            replacedPerhitungan();
        },
    });
}
...
Beberapa file yang perlu diperhatikan :
  • "./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 seperti http://localhost:8000/admin/menu maka URL/halaman aktif ialah menu 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 contoh http://localhost:8000/admin/testStateManagement/detail ke halaman testStateManagement maka cukup ./.

Kekurangan

Boilerplate ini mempunyai kekurangan tetapi dalam kekurangan tersebut ada cara mengatasi dan alternatif.

Menubar

Kekurangan dari Menubar ini ketika menu lain diklik maka focus akan selalu dimenu pertama.

Bukti

menu

Dalam Bukti tersebut fokus menu ke File2 sebelum adanya bug tersebut sudah diperbaiki #3294 tetapi saat memakai Slots dari item bug tersebut muncul.

Cara Mengatasi :

Penggunaan Menubar ini ada 2 file yaitu :

  • AppTopbar.vue

  • Sebagai tema Public Normal.

  • AppHeader.vue

  • Sebagai tema Public Landing.

Direktori Folder Tema

[project_folder]/
`-- resources/
    `-- js/
        `-- Layouts/
            |-- Public/
            |   `-- AppTopbar.vue
            `-- Landing/
                `-- AppHeader.vue
    
AppTopbar.vue

Cara mengatasi dari bug tersebut menambahkan kode CSS.

Beberapa kode yang perlu diperhatikan yaitu :
  • :deep

  • Kode tersebut untuk mencari class dari parent tersebut maka akan diterapkan style sesuai yang diinginkan.

.menubar-normal :deep(.p-menuitem-content) {
    background: transparent !important;
}

.menubar-normal :deep(.p-menuitem-content:hover) {
    color: #495057 !important;
    background: #e9ecef !important;
}
AppHeader.vue

Cara mengatasi dari bug tersebut menambahkan kode CSS.

Beberapa kode yang perlu diperhatikan yaitu :
  • :deep

  • Kode tersebut untuk mencari class dari parent tersebut maka akan diterapkan style sesuai yang diinginkan.

.menubar-landing :deep(.p-menuitem-content) {
    background: transparent !important;
}

.menubar-landing :deep(.p-menuitem-content:hover) {
    color: #495057 !important;
    background: #e9ecef !important;
}
Alternatif : (belum ada)

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 artinya header 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 file Menu.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 artinya header 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>
Beberapa kode yang perlu diperhatikan yaitu :
  • this.thisInject.close()

  • Kode ini digunakan untuk memanggil fungsi onClose dari file Menu.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

menu

Ketika hanya ada 1 button.

Hasil

menu

Ketika dikombinasikan dengan tag element a href.

Hasil

menu

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

menu

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)

Image

Kekurangan dari Image ketika gambar yang tampil menggunakan nama gambar teks atau kalimat maka kalimat tersebut menjadi 1 bagian saat nama gambar tersebut panjang dan background bayangan dari icon mata saat hover tidak kelihatan hal ini jika dipakai dengan tabel.

Bukti

imagepreview
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
Perintah tersebut berguna untuk menginstall npm-check-updates.
Masukkan perintah berikutnya:
ncu -u
Perintah tersebut berguna untuk cek library yang terbaru dari setiap library yang telah diinstal.
Masukkan perintah berikutnya:
npm install
Perintah tersebut instal library versi terbaru dari setiap library yang telah diinstal.
(Source)

Composer.json

Buka Command Prompt di dalam direktori folder Boilerplate tersebut
Masukkan perintah :
composer update
Perintah tersebut berguna untuk instal library yang versi terbaru dari setiap library yang telah diinstal.
(Source)

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 ❌

    • Simpan Role