Membuat Fitur “Load More” di Laravel Menggunakan VueJS


Beberapa situs, menampilkan fitur “load more” pada aplikasi web mereka. Bagi kalian yang belum tahu seperti apa fitur yang dimaksud, gambar bergerak di bawah mungkin bisa mempermudah untuk memahaminya.

Pada dasarnya, fitur “load more” tidak lebih dari sekadar pagination dengan beberapa modifikasi. Bedanya, jika pada mode pagination data sebelumnya akan hilang, maka pada fitur load more data sebelumnya tetap ada dan ditambah dengan data baru yang sudah dimuat ulang.

Untuk membuat fitur “load more”, kita tidak perlu menambahkan package PHP baru, atau modul NodeJS baru. Kita hanya perlu memanfaatkan fitur pagination yang sudah tersedia di Laravel sebagai backend, kemudian menggunakan komponen VueJS (+ Axios untuk AJAX request) sebagai frontend.

Alangkah baiknya, kalian sudah paham sedikit mengenai pagination di Laravel dan komponen di VueJS. Saya pernah mempublikasikan sebuah tulisan membahas dua hal tersebut secara bersamaan dalam judul AJAX Pagination dengan Laravel dan VueJS.

***

Tujuan utama dalam tulisan ini adalah membuat fitur “load more” yang serupa, namun dari segi bentuk sedikit berbeda. Kita akan memanfaatkan user (table, seeder) yang telah disediakan oleh Laravel sebagai data utama. Datanya sendiri akan ditampilkan dalam format table HTML.

Hasil akhir contoh aplikasi

Instalasi Laravel dan Module NodeJS

Instal framework Laravel terlebih dahulu menggunakan Laravel Installer. Sebagai alternatif, kalian juga dapat menginstalnya secara langsung menggunakan Composer.

Masuk ke direktori instalasi Laravel dan instal modul Node menggunakan NPM atau Yarn. Dalam keseharian, saya lebih sering menggunakan Yarn, tak terkecuali dalam tulisan kali ini.

Membuat API User

Sebelum masuk ke dalam pembuatan komponen di VueJS, terlebih dahulu kita menyiapkan API untuk memroses data user yang akan digunakan di frontend.

Buat controller baru dengan nama UserController dalam direktori App/Http/Controllers/Api. Untuk kecepatan, kalian bisa menggunakan Artisan console untuk pembuatan.

$ php artisan make:controller Api/UserController

Tambahkan method baru bernama index() dalam controller tersebut. Method ini berfungsi untuk mengambil semua data user dan mengembalikannya ke dalam bentuk pagination.

/**
 * Show all users separated by pagination.
 *
 * @param Request $request
 *
 * @return JsonResponse
 */
public function index(Request $request): JsonResponse
{
    $users = User::orderBy('name')
        ->simplePaginate((int) $request->get('limit', 20));

    return response()
        ->json($users)
        ->withCallback($request->callback);
}

Sebagai informasi, method withCallback($name) berfungsi untuk me-render output menjadi JSONP. JSONP punya manfaat untuk menghindari kesalahan cross domain dari AJAX request apabila frontend dan backend berada di domain yang berbeda. Output JSONP dieksekusi jika menerima request dengan nama callback, seperti query string ?callback=users misalnya.

Sebagai alternatif, kalian juga dapat menuliskannya sebagai berikut:

return response()->jsonp($request->callback, $user);

Contoh respons JSONP dapat diklik pada tautan berikut.

Jangan lupa mendaftarkan controller tersebut pada route agar dapat diakses melalui peramban. Dalam aplikasi saya menambahkan pada routes/api.php dengan definisi sebagai berikut.

Route::get('user', 'Api\UserController@index');

Selanjutnya, route dalam diakses melalui peramban dengan tautan domain.com/api/user.

Kembali ke method index() di UserController. Method ini secara bawaan mengembalikan data user dalam bentuk JSON. Karena menggunakan pagination dengan nama method simplePaginate(), maka formatnya otomatis akan dibentuk seperti potongan di bawah.

{
  "current_page": 1,
  "data": [
    {
      "id": 60,
      "name": "Alayna Zemlak",
      "email": "chilll@example.com",
      "created_at": "2018-03-14 07:49:38",
      "updated_at": "2018-03-14 07:49:38"
    }
  ],
  "first_page_url": "http://loadmore.test/api/user?page=1",
  "from": 1,
  "next_page_url": "http://loadmore.test/api/user?page=2",
  "path": "http://loadmore.test/api/user",
  "per_page": 1,
  "prev_page_url": null,
  "to": 1
}

Ada dua atribut yang berperan penting dalam fitur ini, yaitu atribut data yang bertipe array dengan isian data user, dan atribut next_page_url yang bertipe string. Ke depannya, dua atribut tersebut punya peran vital dalam alur aplikasi.

Adapun, alur aplikasinya sendiri cukup sederhana. Ketika pertama kali komponen dimuat, maka klien akan melakukan permintaan data user ke API. Jika data tidak kosong dan ada data di halaman berikutnya, maka akan ditampilkan tombol atau tautan “load more”. Apabila tombol atau tautan “load more” diklik, maka akan menjalankan method untuk melakukan permintaan kembali ke API user dengan basis URL yang diambil dari atribut next_page_url.

Nah, bagian selanjutnya yang perlu diperhatikan adalah, ketika respons dari permintaan berhasil didapat, alih-alih menimpa data sebelumnya, di sini, proses yang dijalankan hanya melakukan beberapa modifikasi nilai. Seperti, menambahkan data baru ke atribut data sebelumnya, serta mengganti nilai atribut next_page_url dengan respons yang baru didapat.

Membuat Komponen VueJS

Tambahkan berkas baru dengan nama Index.vue pada direktori resources/assets/js/components/user. Dalam komponen tersebut, tambahkan skrip seperti pada snippet di bawah.

<script>
  export default {
    name: 'UserIndex',

    data() {
      return {
        users: {}
      }
    },

    mounted() {
      this.paginate()
    },

    methods: {
      paginate(url = '') {
        axios.get(url ? url : 'api/user')
          .then(response => {  
            if (! this.users.data) {
              this.users = response.data
            }
            else {
              this.users.data.push(...response.data.data)
              this.users.next_page_url = response.data.next_page_url
            }
          })
          .catch(e => console.error(e))
      }
    }
  }
</script>

Saya coba jelaskan sedikit demi sedikit potongan skrip di atas.

Pertama, kita perlu mendefinisikan data komponen (baris 7) untuk menampung objek yang diminta ke API user, dalam kasus namanya adalah users.

Selanjutnya, menambahkan sebuah method baru dengan nama paginate(url). Method ini menerima satu argumen dengan nama url. Apabila  saat pemanggilan tidak didefinisikan, maka url akan didefinisikan secara manual. Dalam method paginate(url) terdapat proses permintaan AJAX menggunakan Axios (mulai baris 17 sampai 27). Modul Axios sendiri sudah tersedia dalam paket instalasi Laravel.

Apabila respons berhasil didapat (baris 19 sampai 25), akan dilakukan pengecekan jika atribut users.data belum ada, maka masukkan objek response dari API ke dalam data users. Selain itu, maka modifikasi dua atribut seperti yang saya singgung di atas, yaitu menambahkan data baru ke dalam objek users.data, dan menimpa nilai atribut users.next_page_url.

Jangan bingung ketika saya menyebut users.data, hal ini sebenarnya merujuk ke atribut data yang merupakan child dari atribut users seperti pada gambar.

Untuk menambahkan objek data baru ke dalam users.data, saya menggunakan fungsi push() di JavaScript dengan argumen objek yang didapat dari respons, ditambah spread syntax di JavaScript. Untuk memahami lebih lanjut spread syntax (tanda …), kalian bisa membacanya pada tulisan eksternal dengan judul 6 Great Uses of the Spread Operator.

Method paginate(url) juga dipanggil dalam method mounted() VueJS. Method mounted() sendiri dijalankan ketika komponen pertam kali dimuat, beriringan dengan itu, method paginate(url) juga dijalankan untuk meminta data user ke API.

Di bagian atas skrip di atas, tambahkan template HTML berikut.

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="card card-default">
        <div class="card-header"><h3>Users</h3></div>
        <div class="card-body no-padding">
          <table v-if="users.data" class="table table-responsive table-hover">
            <tr>
              <th>Name</th>
              <th>Email</th>
              <th>Created</th>
              <th>Updated</th>
            </tr>

            <tbody>
              <tr v-for="(user, index) in users.data">
                <td>{{ user.name }}</td>
                <td>{{ user.email }}</td>
                <td>{{ user.created_at }}</td>
                <td>{{ user.updated_at }}</td>
              </tr>
            </tbody>
          </table>
        </div>

        <div v-if="users.next_page_url" class="card-footer">
          <button @click.prevent="paginate(users.next_page_url)" type="button" class="btn btn-primary btn-block">Load More</button>
        </div>
      </div>
    </div>
  </div>
</template>

Sekali lagi saya ingatkan bagi yang belum familiar dengan komponen VueJS, template HTML dan JavaScript berada pada berkas yang sama, yaitu Index.vue. Saya pisah dalam tulisan untuk memudahkan penjelasan dan syntax highlighting.

Pada template, saya menggunakan Bootstrap 4 bawaan Laravel versi 5.6. Beberapa class agak sedikit berbeda dengan Bootstrap versi 3, seperti class card yang menggantikan class panel misalnya. Perbedaan ini saya rasa tidak terlalu jauh seandainya kalian menggunakan Bootstrap versi 3, saya yakin kalian bisa menyesuaikan sendiri.

Pada tag tr dalam template, kita melakukan looping data objek users.data yang nantinya akan didapatkan ketika pertama kali komponen dimuat.

Di bagian bawahnya, terdapat tag button yang berfungsi untuk memanggil method paginate(url), method ini akan dipanggil ketika event tombol diklik. Parameter method paginate diambil dari atribut users.next_page_url.

Langkah selanjutnya, daftarkan komponen Index.vue pada berkas resources/assets/js/app.js dengan menambahkan potongan skrip di bawah.

Vue.component('user-index', require('./components/user/Index.vue'));

Pastikan untuk menambahkan skrip di atas setelah Vue dimuat. Sebagai contoh, kalian dapat lihat langsung pada skrip berikut.

Troubleshoot: Jika kalian mendapati pesan kesalahan seperti “[Vue warn]: Failed to mount component: template or render function not defined.” pada console peramban saat komponen dijalankan, cobalah untuk mengubah skrip di atas seperti berikut.

Vue.component('user-index', require('./components/user/Index.vue').default);

Jalankan perintah npm run dev atau yarn dev melalui terminal untuk mengkompilasi komponen. Pastikan komponen berhasil dikompilasi sebelum menjalankan aplikasi melalui peramban. Untuk tahap development, kalian dapat menggunakan perintah npm run watch-poll atau yarn watch-poll. Perintah ini berfungsi untuk mengkompilasi secara otomatis setiap ada perubahan pada berkas komponen.

Sampai di sini, kalian sudah dapat menggunakan komponen tersebut di manapun selama berada dalam cakupan Vue instance. Cukup menambahkan tag <user-index></user-index> pada template Blade atau lainnya.

Untuk contoh implementasi, saya membuat controller baru dengan nama UserController. Di dalamnya terdapat method index() yang memuat view dengan nama index.blade.php. Blade ini menggunakan basis template app.blade.php yang didapatkan ketika meng-generate auth di Laravel (php artisan make:auth). Kemudian, di dalam index.blade.php cukup tambahkan tag <user-index> untuk memuat komponen yang telah dibuat sebelumnya.

Demo dan Repositori

Bagi kalian yang ingin mempelajari langsung dari skrip atau ingin mengembangkan lebih lanjut, saya sudah menyediakan repositori yang berisi contoh aplikasi fitur “load more”. Contoh aplikasi bersifat bebas untuk kalian gunakan.

Silakan menuju tautan berikut untuk mengakses repositori.

Selain repositori, saya juga sudah menyediakan demo langsung yang dapat kalian coba tanpa harus menginstal aplikasi di mesin lokal. Klik tautan berikut untuk melihat demo.

***

Terakhir, saya sangat senang bisa berdiskusi dengan pembaca, apalagi membantu menyelesaikan kasus yang dialami, khususnya tentang bahasan pada tulisan ini. Namun, sebelum bertanya, pastikan bahwa informasi yang diberikan cukup komplit untuk kemudahan debugging. 😉

1 comment

Tinggalkan Balasan