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

Raymond Camden
Raymond Camden

Giriş

IndexedDB, istemci tarafında veri depolamanın güçlü bir yoludur. Henüz bakmadıysanız konuyla ilgili faydalı MDN eğitimlerini okumanızı öneririz. Bu makalede API'ler ve özellikler hakkında bazı temel bilgilerin olduğu 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. Artık bu işlem 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 kullanıcı arayüzünün Otomatik tamamlama denetimini kullanabiliriz. Otomatik tamamlama denetimi, temel listelere ve veri dizilerine izin verirken herhangi bir veri kaynağına izin veren bir API'ye sahiptir. IndexedDB verilerimize bağlanmak için bunu nasıl kullanabileceğimizi göstereceğiz.

Başlarken

Bu demonun birden çok bölümü var. İlk olarak 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 olarak, otomatik tamamlama için kullanılacak "ad" alanı gelir. Bu özellik devre dışı olarak yüklenir ve daha sonra JavaScript aracılığıyla etkinleştirilir. Yanındaki aralık, kullanıcıya güncellemeler sağlamak için ilk başlangıç noktası sırasında kullanılır. Son olarak, otomatik öneriden bir çalışan seçtiğinizde displayÇalışan kimliğine sahip div kullanılır.

Şimdi JavaScript'e bakalım. Burada özetlenecek çok şey var, o yüzden adım adım ele alacağız. Kodun tamamını görmek için sonuna gidebilirsiniz.

Öncelikle, IndexedDB'i destekleyen tarayıcılarda 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şken:

var db;
var template;

Şimdi jQuery document ready 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>

Bu değer daha sonra 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 bir koleksiyonu olmak üzere birçok nesneStore 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 gerekiyor. 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, handSeed adlı bir işlev ç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ürebilen ve bize toplu kayıt almamız için bir yol sunan 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. Kullanıcı başka işlemler yapmazken bu 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 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ç sunucu (ya da bizim örneğimizde sanal arka uç sunucu) yerine 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 otomatik tamamlama eklentisini bağlama. jQuery kullanıcı arayüzünün çoğunda olduğu gibi, temel bir HTML öğesiyle başlar ve bu öğede bir oluşturucu yöntem ç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ı tüm ihtiyaçları 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.

Salt okunur bir işlem, sonuç adı verilen bir dizi ve sonucu otomatik tamamlama denetimine ileten bir oncomplete işleyici oluşturarak işe başlarız.

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, kullanıcının tümünü 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, seçilen eşleşmenin ayrıntılı görünümünün gösterilmesini de desteklemek istiyoruz. Otomatik tamamlamayı oluştururken daha önceki Handlebars şablonunu kullanan bir seçici işleyici belirttik.