Elemen UI databinding dengan IndexedDB

Raymond Camden
Raymond Camden

Pengantar

IndexedDB adalah cara yang efektif untuk menyimpan data di sisi klien. Jika Anda belum mempelajarinya, sebaiknya baca tutorial MDN yang bermanfaat untuk topik tersebut. Artikel ini mengasumsikan Anda memiliki pengetahuan dasar tentang API dan fitur-fiturnya. Bahkan jika Anda belum pernah melihat IndexedDB, semoga demo dalam artikel ini akan memberi Anda gambaran tentang apa yang dapat dilakukan dengan IndexedDB.

Demo kami adalah bukti sederhana dari konsep aplikasi intranet untuk sebuah perusahaan. Aplikasi ini akan memungkinkan karyawan mencari karyawan lain. Untuk memberikan pengalaman yang lebih cepat dan lebih ringkas, {i>database<i} karyawan disalin ke komputer klien dan disimpan menggunakan IndexedDB. Demo ini hanya menyediakan penelusuran bergaya pelengkapan otomatis dan menampilkan satu record karyawan, tetapi bagusnya setelah data ini tersedia di klien, kita juga bisa menggunakannya dalam beberapa cara lain. Berikut ini garis besar dasar tentang apa yang perlu dilakukan aplikasi kita.

  1. Kita harus menyiapkan, dan menginisialisasi, sebuah instance IndexedDB. Biasanya ini cukup mudah, tetapi membuatnya berfungsi di Chrome dan Firefox ternyata sedikit rumit.
  2. Kita perlu melihat apakah kita memiliki data, dan jika tidak, unduh data tersebut. Sekarang biasanya hal ini dilakukan melalui panggilan AJAX. Untuk demo ini, kami telah membuat class utilitas sederhana untuk menghasilkan data palsu dengan cepat. Aplikasi perlu mengenali saat membuat data ini 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. Bila aplikasi sudah siap, kita dapat menggunakan kontrol Autocomplete UI jQuery UI untuk menyinkronkan dengan IndexedDB. Meskipun kontrol Autocomplete memungkinkan daftar dasar dan array data, kontrol ini memiliki API untuk memungkinkan sumber data apa pun. Kita akan mendemonstrasikan bagaimana cara menggunakannya untuk terhubung ke data IndexedDB.

Memulai

Kami memiliki beberapa bagian dalam demo ini, jadi untuk memulai dengan sederhana, 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 UI ini yang penting bagi kita. Pertama adalah kolom, "nama" yang akan digunakan untuk pelengkapan otomatis. Muatan dinonaktifkan dan akan diaktifkan nanti melalui JavaScript. Span di sebelahnya digunakan selama seed awal untuk memberikan update kepada pengguna. Terakhir, div dengan ID displayKaryawan akan digunakan ketika Anda memilih karyawan dari saran otomatis.

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

Pertama - ada beberapa masalah awalan yang harus kita khawatirkan di antara browser yang mendukung IndexedDB. Berikut adalah beberapa kode dari dokumentasi Mozilla yang dimodifikasi untuk memberikan alias sederhana bagi komponen IndexedDB inti yang dibutuhkan 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 seluruh demo:

var db;
var template;

Sekarang kita akan mulai dengan blok ready dokumen jQuery:

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

Demo kami menggunakan Handlebars.js untuk menampilkan detail karyawan. Itu tidak digunakan sampai nanti, tetapi kita bisa melanjutkan dan mengompilasi {i>template<i} kita sekarang dan menyelesaikannya. Kami memiliki blok skrip yang disiapkan sebagai jenis yang dikenali Handlebar. Ini tidak terlalu bagus, tetapi membuatnya lebih mudah untuk menampilkan HTML dinamis.

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

Kode ini kemudian dikompilasi kembali dalam JavaScript kita seperti berikut:

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

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

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 seperti tabel database. Satu IndexedDB mungkin memiliki banyak objectStore, masing-masing berisi kumpulan objek terkait. Demo kita sederhana dan hanya memerlukan satu objectStore yang kita sebut "employee". Saat indexDB dibuka untuk pertama kalinya, atau saat Anda mengubah versi dalam kode, peristiwa onupgradeneeded akan dijalankan. Kita dapat menggunakan ini 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 objek tersebut berisi karyawan. Jika tidak, kami hanya akan melakukannya. Panggilan createIndex penting. Kita harus memberi tahu IndexedDB metode apa, selain kunci, yang akan kita gunakan untuk mengambil data. Kita akan menggunakan kunci yang disebut {i>searchkey<i}. Hal ini akan dijelaskan secara singkat.

Peristiwa onungradeneeded akan otomatis berjalan saat pertama kali kita menjalankan skrip. Setelah dieksekusi, atau dilewati dalam proses selanjutnya, pengendali onsuccess akan dijalankan. Kita telah menentukan pengendali error sederhana (dan buruk) dan kemudian memanggil handleSeed.

Jadi sebelum kita lanjutkan, mari kita tinjau dengan cepat apa yang terjadi di sini. Kita membuka {i>database<i}. Kita memeriksa apakah penyimpanan objek telah ada. Jika tidak, kita akan membuatnya. Terakhir, kita memanggil fungsi bernama handleSeed. Sekarang, mari kita perhatikan bagian penyebaran data di demo kita.

Berikan Beberapa Data.

Seperti disebutkan di bagian pengantar artikel ini, demo ini membuat ulang aplikasi gaya intranet yang perlu menyimpan salinan semua karyawan yang dikenal. Biasanya ini akan melibatkan pembuatan API berbasis server yang bisa mengembalikan jumlah karyawan dan menyediakan cara bagi kita untuk mengambil kumpulan catatan. Anda dapat membayangkan layanan sederhana yang mendukung jumlah awal dan mengembalikan 100 orang sekaligus. Tindakan ini dapat berjalan secara asinkron di latar belakang saat pengguna tidak melakukan hal lain.

Untuk demo, kita melakukan sesuatu yang sederhana. Kita melihat berapa banyak objek, jika ada, di IndexedDB. Jika berada di bawah angka tertentu, kami akan membuat pengguna palsu. Jika tidak, kita akan dianggap selesai dengan bagian seed dan dapat mengaktifkan bagian pelengkapan otomatis dari demo ini. 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 sedikit rumit karena kita memiliki beberapa operasi yang dirantai satu sama lain, jadi mari kita perinci:

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 count API - yang seperti yang bisa Anda duga - melakukan penghitungan.

onsuccess = function(e) {

Setelah selesai - jalankan callback ini. Di dalam callback, kita bisa mendapatkan nilai hasil yang merupakan jumlah objek. Jika jumlahnya nol, kita kemudian memulai proses awal.

Kita menggunakan div status yang disebutkan sebelumnya untuk memberikan pesan kepada pengguna bahwa kita akan mulai mendapatkan data. Berkat sifat asinkron IndexedDB, kita telah menyiapkan variabel sederhana, selesai, yang akan melacak penambahan. Kita mengulang dan memasukkan orang palsu itu. Sumber fungsi tersebut tersedia di 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. Tetapi kita memiliki persyaratan khusus agar dapat menelusuri data kita. IndexedDB tidak menyediakan cara untuk mencari item dengan cara yang tidak peka huruf besar/kecil. Oleh karena itu, kita membuat salinan kolom nama belakang ke properti baru, yaitu kunci penelusuran. 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, hal itu dilakukan di sini dan berbeda dari server {i>back-end<i} (atau dalam kasus ini, server {i>back-end<i} imajiner).

Untuk melakukan penambahan database secara berperforma tinggi, Anda harus menggunakan kembali transaksi untuk semua batch operasi tulis. Jika Anda membuat transaksi baru untuk setiap penulisan, browser dapat menyebabkan disk menulis untuk setiap transaksi, dan itu akan membuat kinerja Anda buruk saat menambahkan banyak item (bayangkan "1 menit untuk menulis 1000 objek" -mengerikan).

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

Membuat Autocomplete

Sekarang bagian yang menyenangkan - terhubung dengan plugin Autocomplete jQuery UI. Seperti kebanyakan UI jQuery, kita mulai dengan elemen HTML dasar dan menyempurnakannya dengan memanggil metode konstruktor di dalamnya. Kami telah memisahkan seluruh proses menjadi sebuah fungsi yang disebut {i>setupAutoComplete<i}. 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 UI jQuery memungkinkan Anda menentukan properti sumber yang dapat disesuaikan untuk memenuhi setiap kemungkinan kebutuhan - bahkan data IndexedDB. API tersebut menyediakan permintaan (pada dasarnya apa yang diketik ke dalam kolom formulir) dan callback respons. Anda bertanggung jawab untuk mengirimkan array hasil kembali ke callback tersebut.

Hal pertama yang kita lakukan adalah menyembunyikan div displayKaryawan. Ini digunakan untuk menampilkan masing-masing karyawan dan jika ada karyawan yang dimuat sebelumnya, untuk menghapusnya. Sekarang kita bisa mulai mencari.

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

Untuk menemukan item yang cocok dengan input kita, mari kita gunakan tip dari pengguna StackOverflow, Fong-Wan Chau: Kita menggunakan rentang indeks berdasarkan input sebagai batas ujung bawah dan input ditambah huruf z sebagai batas rentang 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 (seperti menjalankan kueri database) dan melakukan iterasi pada hasilnya. Kontrol pelengkapan otomatis UI jQuery memungkinkan Anda menampilkan semua jenis data yang diinginkan, tetapi memerlukan kunci nilai minimal. Kita tetapkan nilai ke versi nama yang diformat dengan baik. Kita juga akan mengembalikan seluruh pengguna. Anda akan tahu alasannya. Pertama, berikut adalah screenshot penggunaan pelengkapan otomatis. Kami menggunakan tema Vader untuk UI jQuery.

Dengan sendirinya, ini sudah cukup untuk menampilkan hasil kecocokan IndexedDB dengan pelengkapan otomatis. Namun, kita juga ingin mendukung tampilan detail kecocokan saat salah satunya dipilih. Kami menentukan pengendali tertentu saat membuat pelengkapan otomatis yang menggunakan template Handlebars dari contoh sebelumnya.