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 dapat memberi Anda gambaran tentang apa yang dapat dilakukan dengan IndexedDB.

Demo kami adalah aplikasi Intranet bukti konsep sederhana untuk perusahaan. Aplikasi ini akan memungkinkan karyawan menelusuri karyawan lain. Untuk memberikan pengalaman yang lebih cepat dan lebih praktis, database karyawan disalin ke komputer klien dan disimpan menggunakan IndexedDB. Demo ini hanya menyediakan penelusuran bergaya pelengkapan otomatis dan menampilkan satu data karyawan, tetapi yang bagus adalah setelah data ini tersedia di klien, kita juga dapat menggunakannya dengan beberapa 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 sedikit rumit.
  2. Kita perlu melihat apakah ada data, dan jika tidak, download data tersebut. Biasanya, hal ini akan dilakukan melalui panggilan AJAX. Untuk demo, kita 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 lanjutan akan menangani operasi sinkronisasi antara klien dan server, tetapi demo ini lebih berfokus pada aspek UI.
  3. Setelah aplikasi siap, kita dapat menggunakan kontrol Autocomplete jQuery UI untuk menyinkronkan dengan IndexedDB. Meskipun kontrol Autocomplete memungkinkan daftar dan array data dasar, kontrol tersebut memiliki API untuk memungkinkan sumber data apa pun. Kita akan menunjukkan cara menggunakannya untuk terhubung ke data IndexedDB.

Memulai

Demo ini memiliki beberapa bagian. Jadi, untuk memulainya 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 penting bagi kita. Pertama adalah kolom "nama" yang akan digunakan untuk pelengkapan otomatis. Fitur ini dimuat dalam keadaan nonaktif dan akan diaktifkan nanti melalui JavaScript. Rentang di sampingnya 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. Ada banyak hal yang perlu dicerna di sini, jadi kita akan melakukannya langkah demi langkah. Kode lengkap akan tersedia di bagian akhir sehingga Anda dapat melihatnya secara keseluruhan.

Pertama-tama, ada beberapa masalah awalan yang harus kita perhatikan di antara browser yang mendukung IndexedDB. Berikut adalah beberapa kode dari dokumentasi Mozilla yang diubah 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;

Berikutnya, beberapa variabel global yang akan kita gunakan dalam demo:

var db;
var template;

Sekarang kita akan memulai dengan blok dokumen jQuery yang siap:

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

Demo kami menggunakan Handlebars.js untuk menampilkan detail karyawan. Hal ini tidak akan digunakan sampai nanti, tetapi kita dapat langsung mengompilasi template sekarang dan menyelesaikannya. Kita telah menyiapkan blok skrip sebagai jenis yang dikenali Handlebars. Ini tidak terlalu canggih, tetapi mempermudah tampilan HTML dinamis.

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

Kemudian, kode ini dikompilasi kembali di JavaScript seperti ini:

//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 mirip dengan tabel database. Satu postingan mungkin memiliki banyak objectStores, masing-masing berisi kumpulan objek terkait. Demo kami sederhana dan hanya memerlukan satu objectStore yang kita 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 objek tersebut berisi karyawan. Jika tidak, kita cukup membuatnya melakukannya. Panggilan createIndex penting. Kita harus memberi tahu IndexedDB metode apa, selain kunci, yang akan kita gunakan untuk mengambil data. Kita akan menggunakan salah satu yang disebut searchkey. Hal ini akan dijelaskan sebentar lagi.

Peristiwa onungradeneeded akan berjalan secara otomatis saat pertama kali menjalankan skrip. Setelah dieksekusi, atau dilewati dalam operasi mendatang, pengendali onsuccess akan dijalankan. Kita memiliki pengendali error sederhana (dan buruk) yang ditentukan, 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, kita akan membuatnya. Terakhir, kita memanggil fungsi bernama handleSeed. Sekarang, mari kita beralih ke bagian penyediaan data dalam demo kita.

Gimme Some Data!

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

Untuk demo, kita akan melakukan sesuatu yang sederhana. Kita melihat jumlah objek, jika ada, yang kita miliki di IndexedDB. Jika di bawah jumlah tertentu, kita cukup membuat pengguna palsu. Jika tidak, kita dianggap telah selesai dengan bagian seed dan dapat mengaktifkan bagian pelengkapan otomatis dari 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 terikat, 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()

Menjalankan count API - yang seperti yang bisa 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 pengguna pesan bahwa kita akan mulai mendapatkan data. Karena sifat asinkron IndexedDB, kita telah menyiapkan variabel sederhana, selesai, 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"
}

Hal ini saja sudah cukup untuk menentukan seseorang. Namun, kami memiliki persyaratan khusus agar dapat menelusuri data. 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, searchkey. Jika Anda ingat, ini adalah kunci yang kita 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 spesifik klien, hal ini dilakukan di sini, bukan di server {i>back-end<i} (atau dalam kasus ini, server {i>back-end<i} fiktif).

Untuk melakukan penambahan database dengan cara yang berperforma tinggi, Anda harus menggunakan kembali transaksi untuk semua operasi tulis berkelompok. Jika Anda membuat transaksi baru untuk setiap operasi tulis, browser dapat menyebabkan operasi tulis disk untuk setiap transaksi, dan hal ini akan membuat performa Anda menjadi buruk saat menambahkan banyak item (misalnya "1 menit untuk menulis 1000 objek"-mengerikan).

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

Membuat Autocomplete

Sekarang saatnya bagian yang menyenangkan - menghubungkan dengan plugin Autocomplete jQuery UI. Seperti sebagian besar UI jQuery, kita memulai dengan elemen HTML dasar dan meningkatkannya dengan memanggil metode konstruktor di dalamnya. Kita telah memisahkan seluruh proses menjadi fungsi yang disebut setupAutoComplete. Mari kita lihat kode tersebut sekarang.

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 kemungkinan 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. Ini digunakan untuk menampilkan setiap karyawan, dan jika sudah dimuat sebelumnya, untuk menghapusnya. Sekarang kita dapat mulai menelusuri.

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 tips dari pengguna StackOverflow, Fong-Wan Chau: Kita menggunakan rentang indeks berdasarkan input sebagai batas akhir bawah dan input ditambah huruf z sebagai batas rentang atas. Perhatikan juga bahwa kami menggunakan huruf kecil untuk mencocokkan data huruf kecil yang kami masukkan.

Setelah selesai, kita dapat membuka kursor (anggap saja 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 kunci nilai minimal. Kita menetapkan nilai 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 digunakan. Kita menggunakan tema Vader untuk jQuery UI.

Dengan sendirinya, hal ini cukup untuk menampilkan hasil kecocokan IndexedDB ke pelengkapan otomatis. Namun, kita juga ingin mendukung tampilan detail kecocokan saat salah satunya dipilih. Kita menentukan pengendali pilihan saat membuat pelengkapan otomatis yang menggunakan template Handlebars dari sebelumnya.