Membuat QR Code scanner dengan Javascript di CodeIgniter 4

Membuat QR code scanner dengan Javascript di CodeIgniter 4 menggunakan library zxing-js atau “zebra crossing”

Include file library nya

<script src="https://unpkg.com/@zxing/library@latest"></script>

Contoh tampilan view nya, disini menggunakan Bootstrap 4

<button class="btn btn-success btn-block mb-2" id="btnScan"><i class="fa fa-camera mr-2"></i> Scan QR Code</button>

<a href="<?= route_to('absensi') ?>" id="btnBatal" class="btn btn-danger btn-block mb-4 d-none"><i class="fa fa-times mr-2"></i> Batal</a>

<div class="form-group mb-2 d-none" id="formKamera">
    <label for="selectKamera">Pilih Kamera</label>
    <select class="form-control" id="selectKamera"></select>
</div>

<div class="d-flex justify-content-center">
    <video class="mb-2 d-none" id="previewKamera" style="width: 300px; height: 300px;"></video>
</div>
  • Pertama tampil hanya button scan saja, untuk button batal, select kamera dan preview kamera di hidden dulu dengan class d-none dari Bootstrap
  • Button batal nge route ke halaman index nya, jadi jika ditekan akan nge refresh
  • Komponen select kamera default kosong, nanti diisi dengan daftar device/kamera yang tersedia
  • Elemen preview video sebagai tampilan kameranya

Pada kasus ini menggunakan SweetAlert untuk menampilkan alert saat scan berhasil, jadi harus include library nya

<script src="//cdnjs.cloudflare.com/ajax/libs/sweetalert/2.1.2/sweetalert.min.js"></script>

Script untuk scanner nya, jangan lupa include library jquery nya

<script src="<?= base_url('assets/vendor/libs/jquery/jquery.js') ?>"></script>
<script>
  // inisialisasi scanner
  const qrCodeReader = new ZXing.BrowserMultiFormatReader();
  const selectKamera = $("#selectKamera");
  let selectedDeviceId = null;

  // event handler button scan saat click
  $("#btnScan").on("click", startScanning);

  // event handler select kamera pada saat change value
  $(document).on("change", "#selectKamera", function () {
    // set selected device id
    selectedDeviceId = $(this).val();
    // lalu reset dan init scanner
    resetAndInitScanner();
  });

  function startScanning() {
    // cek jika qrCodeReader tidak null, maka reset dan init scanner
    if (qrCodeReader) {
      resetAndInitScanner();
    }
  }

  function resetAndInitScanner() {
    // reset scanner
    qrCodeReader.reset();
    // tampilkan form kamera, preview kamera, dan button batal
    $("#formKamera, #previewKamera, #btnBatal").removeClass("d-none");
    // init scanner
    initScanner();
  }

  function initScanner() {
    qrCodeReader
      .listVideoInputDevices() // ambil list video input devices
      .then(handleVideoInputDevices) // lalu handle video input devices
      .catch(handleError); // handle error
  }

  // fungsi untuk handle video input devices
  function handleVideoInputDevices(videoInputDevices) {
    // jika video input devices lebih dari 0
    if (videoInputDevices.length > 0) {
      // update select kamera dengan list video input devices
      updateSelectKamera(videoInputDevices);

      // decode sekali (tidak berulang) dari video device terpilih ke preview kamera
      qrCodeReader
        .decodeOnceFromVideoDevice(selectedDeviceId, "previewKamera")
        .then(handleScanResult) // lalu handle hasil scan
        .catch(handleError);
    } else {
      // jika video input devices tidak ditemukan, maka tampilkan alert
      alert("Kamera tidak ditemukan!");
    }
  }

  // fungsi untuk update select kamera dengan list video input devices
  function updateSelectKamera(videoInputDevices) {
    // kosongkan select kamera
    selectKamera.empty();

    // loop video input devices
    videoInputDevices.forEach((element) => {
      // tambahkan option ke select kamera dengan value deviceId dan label device label
      const option = $("<option>").text(element.label).val(element.deviceId);

      // jika deviceId sama dengan selectedDeviceId, maka set option selected true
      if (element.deviceId == selectedDeviceId) {
        option.prop("selected", true);
      }

      // tambahkan option ke select kamera
      selectKamera.append(option);
    });
  }

  // fungsi untuk handle hasil scan
  function handleScanResult(result) {
    console.log(result.text);

    // result.text berisi data dari qr code
    // sampai sini scan qr code sudah berhasil
    // bisa lakukan apa saja dengan hasilnya

    // script dibawah akan mengirim post ke controller CodeIgniter 4 untuk update data
    // pada contoh kasus yang digunakan, qr code nya berisi URL untuk update data

    const formData = new FormData();
    // contoh jika ingin mengirim data lewat form body nya
    // formData.append('qr_code', result.text);

    // pada kasus yang digunakan hanya mengirim post dengan body kosong

    // kirim post request ke URL yang didapatkan dari qr code
    fetch(result.text, {
      method: "POST",
      body: formData,
    })
      .then((response) => response.json())
      .then((data) => {
        // response dari controller CI berupa JSON
        if (data.success) {
          // tampilkan alert success menggunakan sweetalert
          swal({
            icon: "success",
            title: "Success",
            text: data.success,
            showConfirmButton: true,
          }).then(() => {
            // redirect ke halaman yang didapat dari response CI
            window.location.href = data.redirect;
          });
        }
      })
      .catch((error) => {
        console.error("Error dalam POST request:", error);
      });
  }

  // untuk handle error
  function handleError(err) {
    console.error(err);
  }

  // cek jika browser tidak support mediaDevices, maka tampilkan alert
  if (!navigator.mediaDevices) {
    alert("Tidak bisa mengakses kamera!");
  }
</script>

Script di controller CodeIgniter untuk kasus diatas

// app/Controllers/Absensi.php

// ...

public function proses()
{
    // cek jika request post
    if ($this->request->is('post')) {
        // contoh jika mengirim data saat post request
        // $qr_code = $this->request->getPost('qr_code');

        // ...
        // proses update ke database
        // ...

        // return response json
        return $this->response->setJSON([
            'success' => 'Absensi masuk berhasil dilakukan',
            'redirect' => route_to('absensi'),
        ]);
    }
}

Untuk contoh route nya

$routes->group('absensi', ['filter' => 'auth'], function ($routes) {
    $routes->get('/', 'Absensi::index', ['as' => 'absensi']);
    $routes->post('proses', 'Absensi::proses', ['as' => 'absensi.proses']);
});

Jika tampil alert Tidak bisa mengakses kamera! atau bahkan tidak jalan sama sekali, ada beberapa kemungkinan:

  1. Browser nya memang tidak support, mungkin browser versi lama
  2. Permission kamera ke block (saat meminta izin akses kamera mungkin kepencet block). Bisa dicek di browser di samping kiri alamat URL, disitu bisa dilihat permission yang digunakan, harusnya ada permission camera dengan status Allow
  3. Penggunaan permission kamera ini hanya bisa jalan di localhost atau di protocol https. Jadi saat testing menggunakan localhost, harusnya bisa jalan
  4. Bahkan menggunakan virtual host pun tidak jalan kalau tidak https, saat saya coba menggunakan URL appscanner.test misalnya, atau dengan IP address laptop 192.168.2.101, kameranya tetap tidak jalan
  5. Jadi harus menggunakan protocol https atau dengan localhost

Saat berhasil dijalankan, scanner akan menampilkan list device/kamera yang tersambung pada perangkat client.

Jika dijalankan di laptop maka akan menampilkan list webcam default laptop, atau webcam external yang tersambung ke laptop.

Jika dijalankan dari Android, maka akan menampilkan list kamera depan atau kamera belakang. Untuk melakukan pengetesan dari mobile device, aplikasi harus di hosting dengan protocol https yang sudah aktif, jika tidak, gak bakal jalan.

Bagaimana kalau ingin menjalankan dari Android tapi tetap localhost, apakah mungkin?

Bisa, dengan bantuan DroidCam. Cukup install aplikasi DroidCam dari Play Store di HP Android nya, dan install juga aplikasi DroidCam client nya di laptop.

Ikuti saja tata cara instalasinya, sudah cukup jelas, Android dan laptop harus dalam satu Wifi network

Setelah laptop dengan DroidCam tersambung, nantinya saat scanner dijalankan di laptop, akan ada tambahan daftar device nya yang dari DroidCam. Jadi saat Android melakukan scan QR Code, aplikasi di localhost akan tetap jalan.

Referensi