IndexedDB ile veri bağlama kullanıcı arayüzü öğeleri

Raymond Camden
Raymond Camden

Giriş

IndexedDB, verileri istemci tarafında depolamak için güçlü bir yöntemdir. Henüz bakmadıysanız konuyla ilgili faydalı MDN eğitimlerini okumanızı öneririz. Bu makalede, API'ler ve özellikler hakkında temel düzeyde bilgi sahibi olduğunuz varsayılmaktadır. IndexedDB'i daha önce görmemiş olsanız bile bu makaledeki demo, bu veri tabanı ile neler yapılabileceği konusunda size fikir verecektir.

Demomuz, bir şirket için basit bir kavram kanıtı intranet uygulamasıdır. Uygulama, çalışanların diğer çalışanları aramasına olanak tanır. Daha hızlı ve daha akıcı bir deneyim sunmak için çalışan veritabanı istemcinin makinesine kopyalanır ve IndexedDB kullanılarak depolanır. Demoda, tek bir çalışan kaydının otomatik tamamlama tarzında aranıp görüntülenmesi sağlanmaktadır. Ancak bu veriler istemcide kullanılabilir hale geldiğinde başka birçok şekilde de kullanılabilir. Uygulamamızın yapması gerekenlerin temel bir özetini aşağıda bulabilirsiniz.

  1. Bir IndexedDB örneği oluşturmamız ve başlatmamız gerekiyor. Bu işlem büyük oranda basittir ancak hem Chrome hem de Firefox'ta çalışmasını sağlamak biraz zordur.
  2. Verilerimiz olup olmadığını kontrol etmemiz ve yoksa indirmemiz gerekiyor. Bu işlem genellikle AJAX çağrıları aracılığıyla yapılır. Demomuzda, hızlıca sahte veri oluşturmak için basit bir yardımcı program sınıfı oluşturduk. Uygulamanın, bu verileri ne zaman oluşturduğunu bilmesi ve kullanıcının o zamana kadar verileri kullanmasını engellemesi gerekir. Bu tek seferlik bir işlemdir. Kullanıcı uygulamayı bir sonraki sefer çalıştırdığında bu işlemi tekrarlaması gerekmez. Daha gelişmiş bir demo, istemci ile sunucu arasındaki senkronizasyon işlemlerini ele alırken bu demo daha çok kullanıcı arayüzü özelliklerine odaklanır.
  3. Uygulama hazır olduğunda, IndexedDB ile senkronize etmek için jQuery UI'nin Otomatik Tamamlama kontrolünü kullanabiliriz. Otomatik tamamlama denetimi, temel listelere ve veri dizilerine izin verirken herhangi bir veri kaynağına izin veren bir API'ye sahiptir. Bunu IndexedDB verilerimize bağlanmak için nasıl kullanabileceğimizi göstereceğiz.

Başlarken

Bu demonun birden fazla bölümü var. Başlangıçta basit bir şekilde HTML bölümüne bakalım.

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

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

Çok fazla değil, değil mi? Bu kullanıcı arayüzünün önemsediğimiz üç temel yönü vardır. İlk alan, otomatik tamamlama için kullanılacak "ad" alanıdır. Bu özellik devre dışı olarak yüklenir ve daha sonra JavaScript aracılığıyla etkinleştirilir. Yanında bulunan span, ilk eklenen veriler sırasında kullanıcıya güncellemeler sağlamak için kullanılır. Son olarak, otomatik öneriden bir çalışan seçtiğinizde displayEmployee kimlikli div kullanılır.

Şimdi JavaScript'e bakalım. Bu konuda çok fazla bilgi var. Bu nedenle, konuyu adım adım ele alacağız. Kodun tamamını görmek için sonuna gidebilirsiniz.

Öncelikle, IndexedDB'i destekleyen tarayıcılar arasında dikkate almamız gereken bazı ön ek sorunları var. Aşağıda, Mozilla dokümanlarındaki bazı kodlar, uygulamamızın ihtiyaç duyduğu temel IndexedDB bileşenleri için basit takma adlar sağlayacak şekilde değiştirilmiştir.

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

Ardından, demo boyunca kullanacağımız birkaç genel değişkeni göreceğiz:

var db;
var template;

Şimdi jQuery belge hazır bloğuyla başlayacağız:

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

Demomuzda, çalışan ayrıntılarını görüntülemek için Handlebars.js kullanılır. Bu daha sonra kullanılacak ancak şablonumuzu şimdi derleyip işleri kolaylaştırabiliriz. Handlebars tarafından tanınan bir tür olarak ayarlanmış bir komut dosyası bloğumuz var. Çok şık olmasa da dinamik HTML'yi görüntülemeyi kolaylaştırır.

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

Ardından bu kod, JavaScript'imizde şu şekilde derlenir:

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

Şimdi IndexedDB ile çalışmaya başlayalım. Öncelikle dosyayı açıyoruz.

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

IndexedDB'e bağlantı açarak verileri okuma ve yazma erişimi elde ederiz. Ancak bunu yapmadan önce bir objectStore'umuz olduğundan emin olmamız gerekir. Nesne deposu, veritabanı tablosuna benzer. Bir IndexedDB'de, her biri ilgili nesnelerin koleksiyonunu barındıran birçok nesne deposu olabilir. Demomuz basittir ve yalnızca "employee" olarak adlandırdığımız bir objectStore'a ihtiyaç duyar. IndexedDB ilk kez açıldığında veya koddaki sürümü değiştirdiğinizde onupgradeneeded etkinliği çalıştırılır. Bu özelliği, nesne depomuzu ayarlamak için kullanabiliriz.

// 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();
};

onupgradeneeded etkinlik işleyici bloğunda, employee değerini içerip içermediğini görmek için bir nesne mağazası dizisi olan objectStoreNames değerini kontrol ederiz. Aksi takdirde, bunu yapmalarını sağlarız. createIndex çağrısı önemlidir. IndexedDB'ye, verileri almak için anahtarlar dışında hangi yöntemleri kullanacağımızı bildirmemiz gerekir. searchkey adlı bir tane kullanacağız. Bu konu birazdan açıklanacaktır.

onungradeneeded etkinliği, komut dosyasını ilk kez çalıştırdığımızda otomatik olarak çalışır. onsuccess işleyicisi, yürütüldükten veya sonraki çalıştırmalarda atlandıktan sonra çalıştırılır. Basit (ve çirkin) bir hata işleyici tanımladık ve ardından handleSeed işlevini çağırıyoruz.

Devam etmeden önce, burada neler olduğunu kısaca gözden geçirelim. Veritabanını açarız. Nesne depomuzun olup olmadığını kontrol ederiz. Aksi takdirde, biz oluştururuz. Son olarak, handleSeed adlı bir işlevi çağırıyoruz. Şimdi, demomuzun veri ekleme bölümüne geçelim.

Gimme Some Data!

Bu makalenin girişinde de belirtildiği gibi, bu demoda bilinen tüm çalışanların bir kopyasının depolanması gereken bir intranet tarzı uygulama yeniden oluşturulmaktadır. Normalde bu, çalışan sayısını döndürebilecek ve kayıt gruplarını almamızı sağlayacak sunucu tabanlı bir API oluşturmayı içerir. Başlangıç sayısını destekleyen ve tek seferde 100 kullanıcı döndüren basit bir hizmet düşünebilirsiniz. Bu işlem, kullanıcı başka şeylerle uğraşırken arka planda eşzamansız olarak çalışabilir.

Demomuzda basit bir işlem yapıyoruz. IndexedDB'imizde kaç tane nesne olduğunu (varsa) görürüz. Belirli bir sayının altındaysa sahte kullanıcılar oluştururuz. Aksi takdirde, başlangıç kısmı tamamlanmış kabul edilir ve demo'nun otomatik tamamlama kısmı etkinleştirilebilir. handleSeed işlevine bakalım.

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();
    }
  };
}

Birbirine zincirlenmiş birden fazla işlem bulunduğundan ilk satır biraz karmaşıktır. Bu nedenle, satırları ayrıntılı olarak inceleyelim:

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

Bu işlem, yeni bir salt okunur işlem oluşturur. IndexedDB ile yapılan tüm veri işlemleri bir tür işlem gerektirir.

objectStore("employee");

Çalışan nesne deposunu alın.

count()

count API'yi çalıştırın. Bu API, tahmin edebileceğiniz gibi bir sayma işlemi gerçekleştirir.

onsuccess = function(e) {

İşlem tamamlandığında bu geri çağırma işlevini yürütün. Geri çağırma içinde, nesne sayısı olan sonuç değerini alabiliriz. Sayı sıfırsa tohumlama sürecimize başlarız.

Daha önce bahsedilen durum div'ini, kullanıcıya veri almaya başlayacağımıza dair bir mesaj vermek için kullanırız. IndexedDB'in asenkron yapısı nedeniyle, eklemeleri izleyen basit bir done değişkeni oluşturduk. Döngü oluşturarak sahte kişileri ekleriz. Bu işlevin kaynağı indirme dosyasında mevcuttur ancak şuna benzeyen bir nesne döndürür:

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

Bu bilgiler tek başına bir kişiyi tanımlamak için yeterlidir. Ancak verilerinizi arayabilmemiz için özel bir şartımız var. IndexedDB, öğeleri büyük/küçük harfe duyarlı olmayan bir şekilde aramanın bir yolunu sağlamaz. Bu nedenle, soyad alanının kopyasını yeni bir mülkte (searchkey) oluştururuz. Hatırlıyorsunuzdur, bu anahtar, verileriniz için dizin olarak oluşturulması gerektiğini söylediğimiz anahtardır.

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

Bu, istemciye özgü bir değişiklik olduğundan arka uç sunucuda (veya bizim durumumuzda hayali arka uç sunucuda) değil, burada yapılır.

Veritabanı eklemelerini performanslı bir şekilde gerçekleştirmek için tüm toplu yazma işlemleri için işlemi yeniden kullanmanız gerekir. Her yazma işlemi için yeni bir işlem oluşturursanız tarayıcı her işlem için disk yazma işlemi gerçekleştirebilir. Bu da çok sayıda öğe eklerken performansınızı çok kötü hale getirir ("1.000 nesneyi yazmak 1 dakika sürer" gibi düşünün).

Kaynak oluşturulduktan sonra uygulamamızın bir sonraki bölümü (setupAutoComplete) tetiklenir.

Otomatik Tamamlama'yı oluşturma

Şimdi eğlenceli kısma geçelim: jQuery UI Autocomplete eklentisini bağlama. jQuery UI'nin çoğunda olduğu gibi, temel bir HTML öğesiyle başlar ve bu öğede bir kurucu yöntemi çağırarak öğeyi geliştiririz. İşlemin tamamını, setupAutoComplete adlı bir işlevde soyutladık. Şimdi bu koda göz atalım.

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));
    }
  });

}

Bu kodun en karmaşık kısmı, kaynak mülkün oluşturulmasıdır. jQuery UI'nin Otomatik Tamamlama denetimi, IndexedDB verileriniz de dahil olmak üzere olası her ihtiyacı karşılayacak şekilde özelleştirilebilen bir kaynak mülk tanımlamanıza olanak tanır. API, isteği (temel olarak form alanına yazılan metin) ve yanıt geri çağırma işlevini sağlar. Bu geri aramaya bir sonuç dizisi göndermekten sorumlusunuz.

İlk olarak displayEmployee div'ini gizliyoruz. Bu div, tek bir çalışanı görüntülemek için kullanılır ve daha önce yüklenmişse temizlenir. Artık aramaya başlayabiliriz.

Öncelikle salt okunur bir işlem, result adlı bir dizi ve sonucu otomatik tamamlama denetimine ileten bir oncomplete işleyicisi oluştururuz.

Girişimizle eşleşen öğeleri bulmak için StackOverflow kullanıcısı Fong-Wan Chau'nun verdiği bir ipucundan yararlanalım: Alt sınır olarak girişe dayalı bir dizin aralığı, üst sınır olarak da girişe z harfi eklenmiş bir dizin aralığı kullanırız. Girdiğimiz küçük harfli verilerle eşleşecek şekilde terimi küçük harfle yazdığımızı da unutmayın.

Bu işlem tamamlandıktan sonra bir imleç açabilir (bunu bir veritabanı sorgusu çalıştırmak gibi düşünebilirsiniz) ve sonuçları iteratif olarak inceleyebiliriz. jQuery UI'nin otomatik tamamlama denetimi, istediğiniz türde verileri döndürmenize olanak tanır ancak en azından bir değer anahtarı gerektirir. Değeri, adın güzel biçimlendirilmiş bir sürümüne ayarlarız. Ayrıca kişinin tamamını da döndürürüz. Bunun nedenini birazdan göreceksiniz. Öncelikle, otomatik tamamlamanın çalışırken görüntüsü aşağıda verilmiştir. jQuery UI için Vader temasını kullanıyoruz.

Bu, IndexedDB eşleşmelerimizin sonuçlarını otomatik tamamlamaya döndürmek için tek başına yeterlidir. Ancak bir maç seçildiğinde maçın ayrıntılı görünümünü göstermeyi de desteklemek istiyoruz. Otomatik tamamlamayı oluştururken daha önceki Handlebars şablonunu kullanan bir seçici işleyici belirttik.