Elemen UI databinding dengan IndexedDB

Raymond Camden
Raymond Camden

Pengantar

IndexedDB adalah cara yang efektif untuk menyimpan data di sisi klien. Jika Anda belum melihatnya, sebaiknya baca tutorial MDN yang bermanfaat tentang topik ini. Artikel ini mengasumsikan beberapa pengetahuan dasar tentang API dan fitur. Meskipun Anda belum pernah melihat IndexedDB sebelumnya, semoga demo dalam artikel ini akan memberi Anda gambaran tentang apa yang dapat dilakukan dengannya.

Demo kami adalah aplikasi Intranet bukti konsep sederhana untuk sebuah perusahaan. Aplikasi ini akan memungkinkan karyawan untuk menelusuri karyawan lain. Untuk memberikan pengalaman yang lebih cepat dan responsif, database karyawan disalin ke perangkat klien dan disimpan menggunakan IndexedDB. Demo ini hanya menyediakan penelusuran bergaya pelengkapan otomatis dan tampilan satu data karyawan, tetapi yang menarik adalah setelah data ini tersedia di klien, kita juga dapat menggunakannya dalam berbagai cara lain. Berikut adalah garis besar dasar tentang hal yang perlu dilakukan aplikasi kita.

  1. Kita harus menyiapkan dan melakukan inisialisasi instance IndexedDB. Sebagian besar proses ini mudah, tetapi membuatnya berfungsi di Chrome dan Firefox terbukti agak rumit.
  2. Kita perlu melihat apakah kita memiliki data, dan jika tidak, mendownloadnya. Biasanya, hal ini dilakukan melalui panggilan AJAX. Untuk demo ini, kami telah membuat class utilitas sederhana untuk membuat data palsu dengan cepat. Aplikasi harus mengenali kapan data ini dibuat dan mencegah pengguna menggunakan data tersebut hingga saat itu. Operasi ini hanya perlu Anda lakukan satu kali. Saat pengguna menjalankan aplikasi lagi, aplikasi tidak perlu melalui proses ini. Demo yang lebih canggih akan menangani operasi sinkronisasi antara klien dan server, tetapi demo ini lebih berfokus pada aspek UI.
  3. Setelah aplikasi siap, kita dapat menggunakan kontrol Pelengkapan Otomatis jQuery UI untuk menyinkronkan dengan IndexedDB. Meskipun kontrol Autocomplete memungkinkan daftar dan array data dasar, kontrol ini memiliki API untuk memungkinkan sumber data apa pun. Kami akan menunjukkan cara menggunakannya untuk terhubung ke data IndexedDB.

Memulai

Demo ini memiliki beberapa bagian, jadi untuk memulai dengan mudah, mari kita lihat bagian HTML.

<form>
  <p>
    <label for="name">Name:</label> <input id="name" disabled> <span id="status"></span>
    </p>
</form>

<div id="displayEmployee"></div>

Tidak banyak, bukan? Ada tiga aspek utama pada UI ini yang perlu kita perhatikan. Pertama adalah kolom, "name" yang akan digunakan untuk pelengkapan otomatis. Tombol ini dimuat dalam keadaan nonaktif dan akan diaktifkan nanti melalui JavaScript. Span di sebelahnya digunakan selama seed awal untuk memberikan update kepada pengguna. Terakhir, div dengan id displayEmployee akan digunakan saat Anda memilih karyawan dari saran otomatis.

Sekarang, mari kita lihat JavaScript-nya. Ada banyak hal yang perlu dipahami di sini, jadi kita akan membahasnya langkah demi langkah. Kode lengkap akan tersedia di akhir sehingga Anda dapat melihatnya secara keseluruhan.

Pertama-tama - ada beberapa masalah awalan yang harus kita khawatirkan di antara browser yang mendukung IndexedDB. Berikut beberapa kode dari dokumentasi Mozilla yang dimodifikasi untuk memberikan alias sederhana bagi komponen IndexedDB inti yang diperlukan aplikasi kita.

window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;
var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;

Selanjutnya, beberapa variabel global yang akan kita gunakan di sepanjang demo:

var db;
var template;

Sekarang kita akan memulai dengan blok siap dokumen jQuery:

$(document).ready(function() {
  console.log("Startup...");
  ...
});

Demo kami menggunakan Handlebars.js untuk menampilkan detail karyawan. Hal ini tidak digunakan sampai nanti, tetapi kita dapat melanjutkan dan mengompilasi template sekarang dan menyelesaikannya. Kita telah menyiapkan blok skrip sebagai jenis yang dikenali Handlebars. Meskipun tidak terlalu mewah, cara ini memudahkan untuk menampilkan HTML dinamis.

<h2>, </h2>
Department: <br/>
Email: <a href='mailto:'></a>

Kemudian, kode ini dikompilasi kembali dalam JavaScript kita seperti ini:

//Create our template
var source = $("#employeeTemplate").html();
template = Handlebars.compile(source);

Sekarang mari kita mulai menggunakan IndexedDB. Pertama - kita buka.

var openRequest = indexedDB.open("employees", 1);

Membuka koneksi ke IndexedDB memberi kita akses untuk membaca dan menulis data, tetapi sebelum melakukannya, kita harus memastikan bahwa kita memiliki objectStore. objectStore mirip dengan tabel database. Satu IndexedDB dapat memiliki banyak objectStore, yang masing-masing menyimpan kumpulan objek terkait. Demo kami sederhana dan hanya memerlukan satu objectStore yang kami sebut “employee”. Saat indexedDB dibuka untuk pertama kalinya, atau saat Anda mengubah versi dalam kode, peristiwa onupgradeneeded akan dijalankan. Kita dapat menggunakannya untuk menyiapkan objectStore.

// Handle setup.
openRequest.onupgradeneeded = function(e) {

  console.log("running onupgradeneeded");
  var thisDb = e.target.result;

  // Create Employee
  if(!thisDb.objectStoreNames.contains("employee")) {
    console.log("I need to make the employee objectstore");
    var objectStore = thisDb.createObjectStore("employee", {keyPath: "id", autoIncrement: true});
    objectStore.createIndex("searchkey", "searchkey", {unique: false});
  }

};

openRequest.onsuccess = function(e) {
  db = e.target.result;

  db.onerror = function(e) {
    alert("Sorry, an unforseen error was thrown.");
    console.log("***ERROR***");
    console.dir(e.target);
  };

  handleSeed();
};

Di blok pengendali peristiwa onupgradeneeded, kita memeriksa objectStoreNames, array penyimpanan objek, untuk melihat apakah berisi employee. Jika tidak, kita cukup membuatnya melakukannya. Panggilan createIndex itu penting. Kita harus memberi tahu IndexedDB metode apa, di luar kunci, yang akan kita gunakan untuk mengambil data. Kita akan menggunakan kunci penelusuran yang disebut searchkey. Hal ini akan dijelaskan sebentar lagi.

Peristiwa onungradeneeded akan berjalan secara otomatis saat pertama kali kita menjalankan skrip. Setelah dieksekusi, atau dilewati dalam eksekusi mendatang, pengendali onsuccess akan dijalankan. Kita telah menentukan pengendali error yang sederhana (dan kurang rapi), lalu kita memanggil handleSeed.

Jadi, sebelum melanjutkan, mari kita tinjau sekilas apa yang terjadi di sini. Kita membuka database. Kita memeriksa apakah penyimpanan objek kita ada. Jika tidak ada, kita akan membuatnya. Terakhir, kita memanggil fungsi bernama handleSeed. Sekarang, mari kita beralih ke bagian penyemaian data dalam demo kita.

Beri saya beberapa data.

Seperti yang disebutkan dalam pengantar artikel ini, demo ini membuat ulang aplikasi bergaya Intranet yang perlu menyimpan salinan semua karyawan yang diketahui. Biasanya, hal ini akan melibatkan pembuatan API berbasis server yang dapat menampilkan jumlah karyawan dan menyediakan cara bagi kita untuk mengambil batch data. Anda dapat membayangkan layanan sederhana yang mendukung jumlah awal dan menampilkan 100 orang sekaligus. Proses ini dapat berjalan secara asinkron di latar belakang saat pengguna melakukan hal lain.

Untuk demo, kita akan melakukan sesuatu yang sederhana. Kita akan melihat berapa banyak objek, jika ada, yang kita miliki di IndexedDB. Jika di bawah angka tertentu, kita akan membuat pengguna palsu. Jika tidak, kita dianggap telah selesai dengan bagian seed dan dapat mengaktifkan bagian pelengkapan otomatis demo. Mari kita lihat handleSeed.

function handleSeed() {
  // This is how we handle the initial data seed. Normally this would be via AJAX.

  db.transaction(["employee"], "readonly").objectStore("employee").count().onsuccess = function(e) {
    var count = e.target.result;
    if (count == 0) {
      console.log("Need to generate fake data - stand by please...");
      $("#status").text("Please stand by, loading in our initial data.");
      var done = 0;
      var employees = db.transaction(["employee"], "readwrite").objectStore("employee");
      // Generate 1k people
      for (var i = 0; i < 1000; i++) {
         var person = generateFakePerson();
         // Modify our data to add a searchable field
         person.searchkey = person.lastname.toLowerCase();
         resp = employees.add(person);
         resp.onsuccess = function(e) {
           done++;
           if (done == 1000) {
             $("#name").removeAttr("disabled");
             $("#status").text("");
             setupAutoComplete();
           } else if (done % 100 == 0) {
             $("#status").text("Approximately "+Math.floor(done/10) +"% done.");
           }
         }
      }
    } else {
      $("#name").removeAttr("disabled");
      setupAutoComplete();
    }
  };
}

Baris pertama agak rumit karena kita memiliki beberapa operasi yang saling terkait, jadi mari kita uraikan:

db.transaction(["employee"], "readonly");

Tindakan ini akan membuat transaksi hanya baca baru. Semua operasi data dengan IndexedDB memerlukan semacam transaksi.

objectStore("employee");

Mendapatkan penyimpanan objek karyawan.

count()

Jalankan API hitung - yang seperti yang dapat Anda tebak - melakukan penghitungan.

onsuccess = function(e) {

Dan setelah selesai, jalankan callback ini. Di dalam callback, kita bisa mendapatkan nilai hasil yang merupakan jumlah objek. Jika jumlahnya nol, kita akan memulai proses seed.

Kita menggunakan div status yang disebutkan sebelumnya untuk memberi tahu pengguna bahwa kita akan mulai mendapatkan data. Karena sifat asinkron IndexedDB, kami telah menyiapkan variabel sederhana, done, yang akan melacak penambahan. Kita melakukan loop dan menyisipkan orang palsu. Sumber fungsi tersebut tersedia dalam download, tetapi menampilkan objek yang terlihat seperti ini:

{
  firstname: "Random Name",
  lastname: "Some Random Last Name",
  department: "One of 8 random departments",
  email: "first letter of firstname+lastname@fakecorp.com"
}

Dengan sendirinya, ini sudah cukup untuk mendefinisikan seseorang. Namun, kami memiliki persyaratan khusus agar dapat menelusuri data kami. IndexedDB tidak menyediakan cara untuk mencari item tanpa peka huruf besar/kecil. Oleh karena itu, kita membuat salinan kolom lastname ke properti baru, searchkey. Jika Anda ingat, ini adalah kunci yang kami katakan harus dibuat sebagai indeks untuk data kita.

// Modify our data to add a searchable field
person.searchkey = person.lastname.toLowerCase();

Karena ini adalah modifikasi khusus klien, modifikasi ini dilakukan di sini, bukan di server backend (atau dalam kasus kita, server backend imajiner).

Untuk melakukan penambahan database secara efisien, Anda harus menggunakan kembali transaksi untuk semua penulisan dalam batch. Jika Anda membuat transaksi baru untuk setiap penulisan, browser dapat menyebabkan penulisan disk untuk setiap transaksi, dan hal itu akan membuat performa Anda menjadi buruk saat menambahkan banyak item (pikirkan "1 menit untuk menulis 1.000 objek"-buruk).

Setelah seed selesai, bagian berikutnya dari aplikasi kita akan diaktifkan - setupAutoComplete.

Membuat pelengkapan otomatis

Sekarang, giliran bagian yang menyenangkan, yaitu menghubungkan dengan plugin Autocomplete jQuery UI. Seperti kebanyakan jQuery UI, kita mulai dengan elemen HTML dasar dan meningkatkannya dengan memanggil metode konstruktor di dalamnya. Kami telah mengabstraksi seluruh proses ke dalam fungsi yang disebut setupAutoComplete. Sekarang, mari kita lihat kode tersebut.

function setupAutoComplete() {

  //Create the autocomplete
  $("#name").autocomplete({
    source: function(request, response) {

      console.log("Going to look for "+request.term);

      $("#displayEmployee").hide();

      var transaction = db.transaction(["employee"], "readonly");
      var result = [];

      transaction.oncomplete = function(event) {
        response(result);
      };

      // TODO: Handle the error and return to it jQuery UI
      var objectStore = transaction.objectStore("employee");

      // Credit: http://stackoverflow.com/a/8961462/52160
      var range = IDBKeyRange.bound(request.term.toLowerCase(), request.term.toLowerCase() + "z");
      var index = objectStore.index("searchkey");

      index.openCursor(range).onsuccess = function(event) {
        var cursor = event.target.result;
        if(cursor) {
          result.push({
            value: cursor.value.lastname + ", " + cursor.value.firstname,
            person: cursor.value
          });
          cursor.continue();
        }
      };
    },
    minLength: 2,
    select: function(event, ui) {
      $("#displayEmployee").show().html(template(ui.item.person));
    }
  });

}

Bagian paling kompleks dari kode ini adalah pembuatan properti sumber. Kontrol Autocomplete jQuery UI memungkinkan Anda menentukan properti sumber yang dapat disesuaikan untuk memenuhi kebutuhan apa pun - bahkan data IndexedDB kami. API memberi Anda permintaan (pada dasarnya apa yang diketik ke dalam kolom formulir) dan callback respons. Anda bertanggung jawab untuk mengirim array hasil kembali ke callback tersebut.

Hal pertama yang kita lakukan adalah menyembunyikan div displayEmployee. Div ini digunakan untuk menampilkan setiap karyawan dan jika sebelumnya sudah dimuat, untuk menghapusnya. Sekarang kita dapat mulai menelusuri.

Kita mulai dengan membuat transaksi hanya baca, array yang disebut result, dan pengendali oncomplete yang hanya meneruskan hasil ke kontrol pelengkapan otomatis.

Untuk menemukan item yang cocok dengan input kita, mari kita gunakan tips dari pengguna StackOverflow, Fong-Wan Chau: Kita menggunakan rentang indeks berdasarkan input sebagai batas bawah dan input ditambah huruf z sebagai batas atas. Perhatikan juga bahwa kita menggunakan huruf kecil untuk istilah tersebut agar cocok dengan data huruf kecil yang kita masukkan.

Setelah selesai, kita dapat membuka kursor (anggap seperti menjalankan kueri database) dan melakukan iterasi pada hasilnya. Kontrol pelengkapan otomatis jQuery UI memungkinkan Anda menampilkan jenis data apa pun yang Anda inginkan, tetapi memerlukan setidaknya kunci nilai. Kita menetapkan nilainya ke versi nama yang diformat dengan baik. Kita juga menampilkan seluruh orang. Anda akan segera mengetahui alasannya. Pertama, berikut adalah screenshot pelengkapan otomatis yang sedang beraksi. Kami menggunakan tema Vader untuk jQuery UI.

Tindakan ini saja sudah cukup untuk menampilkan hasil kecocokan IndexedDB ke pelengkapan otomatis. Namun, kita juga ingin mendukung tampilan detail pertandingan saat salah satu pertandingan dipilih. Kami menentukan pengendali pilihan saat membuat pelengkapan otomatis yang menggunakan template Handlebars dari sebelumnya.