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

Raymond Camden
Raymond Camden

Giriş

IndexedDB, verileri istemci tarafında depolamanın güçlü bir yoludur. Henüz yapmadıysanız konuyla ilgili yararlı MDN eğiticilerini okumanızı öneririz. Bu makalede API'ler ve özelliklerle ilgili bazı temel bilgilere sahip olunduğu varsayılmaktadır. Daha önce IndexedDB'yi görmemiş olsanız bile, bu makaledeki demonun, bununla neler yapabileceğinize dair size bir fikir vereceğini umuyoruz.

Demomuz, bir şirketin basit bir kavram kanıtlama uygulamasıdır. Uygulama, çalışanların diğer çalışanları aramasına olanak tanır. Daha hızlı ve daha hızlı bir deneyim sağlamak amacıyla çalışan veritabanı, istemcinin makinesine kopyalanır ve IndexedDB kullanılarak depolanır. Demo, tek bir çalışan kaydının otomatik tamamlama tarzında bir arama ve görüntüleme olanağı sunuyor. Ancak güzel olan şey, bu verileri istemcide kullanılabilir hale geldikten sonra başka şekillerde de kullanabilmemiz. Uygulamamızın ne yapması gerektiğini ana hatlarıyla burada bulabilirsiniz.

  1. IndexedDB örneği oluşturup başlatmamız gerekiyor. Çoğunlukla bu işlem basittir, ancak hem Chrome'da hem de Firefox'ta çalışmasının biraz zor olduğu kanıtlanmıştır.
  2. Elimizde herhangi bir veri olup olmadığını kontrol etmemiz, varsa verileri indirmemiz gerekiyor. Artık bu işlem genellikle AJAX çağrılarıyla yapılır. Demomuz için hızlı bir şekilde sahte veriler oluşturmak üzere basit bir yardımcı program sınıfı oluşturduk. Uygulamanın, bu verileri oluştururken bunu tanıması ve kullanıcının o zamana kadar verileri kullanmasını engellemesi gerekir. Bu tek seferlik bir işlemdir. Kullanıcı uygulamayı tekrar çalıştırdığında, uygulamanın bu işlemden geçmesi gerekmez. İstemci ve sunucu arasındaki senkronizasyon işlemleri, daha gelişmiş bir demoyla yürütülecektir. Ancak bu demo daha çok kullanıcı arayüzü konusuna odaklanmaktadı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 tüm veri kaynaklarına izin veren bir API'si vardır. IndexedDB verilerimize bağlanırken bunu nasıl kullanabileceğimizi göstereceğiz.

Başlayın

Bu demoda birden fazla bölüm olduğundan, basit bir şekilde başlamak için 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 önem verdiğimiz üç temel özelliği vardır. Birincisi, otomatik tamamlamada kullanılacak "name" (ad) alanıdır. Devre dışı olarak yüklenir ve daha sonra JavaScript aracılığıyla etkinleştirilir. Yanındaki aralık, kullanıcıya güncelleme sağlamak için ilk başlangıç noktası sırasında kullanılır. Son olarak, otomatik öneriden bir çalışan seçtiğinizde displayEmploy kimliğine sahip div kullanılır.

Şimdi JavaScript'e bir göz atalım. Burada özümsenecek çok şey olduğundan bunları adım adım ele alacağız. Tamamını görebilmeniz için kodun tamamı sonda yer alacaktır.

Öncelikle, IndexedDB'yi destekleyen tarayıcılarda dikkat etmemiz gereken bazı önek sorunları var. Uygulamamızın ihtiyaç duyduğu temel IndexedDB bileşenleri için basit takma adlar sağlamak üzere değiştirilmiş, Mozilla dokümanlarından bazı kodları burada bulabilirsiniz.

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

Şimdi, demo boyunca kullanacağımız birkaç genel değişken:

var db;
var template;

Şimdi jQuery belgesine hazır blokla başlayacağız:

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

Demomuzda, çalışan ayrıntılarını göstermek için Handlebars.js kullanılmıştır. Bu kod ileri bir zamana kadar kullanılmayacaktır. Ancak, şablonumuzu şimdi derleyip bu işlere son verebiliriz. Gidon tarafından tanınan tür olarak bir komut dosyası bloğu ayarlanmış. Çok süslü olmasa da dinamik HTML'yi görüntülemeyi kolaylaştırır.

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

Bu, daha sonra JavaScript'te aşağıdaki şekilde derlenir:

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

Şimdi IndexedDB ile çalışmaya başlayalım. İlk olarak, dosyayı açıyoruz.

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

IndexedDB ile bağlantı açmak, verileri okuma ve yazma erişimimiz olmasını sağlar. Ancak bunu yapmadan önce bir nesne Depomuz olduğundan emin olmamız gerekir. NesneStore, veritabanı tablosu gibidir. Bir IndexedDB'nin her biri ilgili nesnelerin bir koleksiyonunu barındıran birçok objectStore olabilir. Demomuz basittir ve yalnızca "çalışan" olarak adlandırdığımız bir nesne deposuna gereksinim duyar. IndexDB ilk kez açıldığında veya koddaki sürümü değiştirdiğinizde, onupgradeneeded bir etkinlik çalıştırılır. Bunu nesneStore'u kurmak 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, çalışan içerip içermediğini görmek için bir nesne deposu dizisi olan objectStoreNames öğesini kontrol ederiz. Öyle değilse, bu şekilde yaparız. createIndex çağrısı önemlidir. IndexedDB'ye, verileri almak için anahtarlar dışında hangi yöntemleri kullanacağımızı söylememiz gerekiyor. Arama tuşu adı verilen bir alan kullanacağız. Bu, birazdan açıklanmıştır.

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

Devam etmeden önce, burada neler olduğuna hızlıca bir göz atalım. Veritabanını açıyoruz. Nesne depomuzun mevcut olup olmadığını kontrol ederiz. Yoksa, gerekli içeriği oluşturuyoruz. Son olarak, "HandleSeed" adlı bir işleve çağrıda bulunuruz. Şimdi demomuzun veri başlangıç kısmına odaklanalım.

Biraz Veri Alın!

Bu makalenin giriş kısmında da belirtildiği gibi bu demo, bilinen tüm çalışanların bir kopyasını depolaması gereken İntranet stili bir uygulamayı yeniden oluşturuyor. Normalde bu, çalışan sayısını döndürebilen ve kayıt gruplarını almamız için bir yol sunabilen, sunucu tabanlı bir API oluşturmayı içerir. Bir başlangıç sayısını destekleyen ve bir defada 100 kullanıcı döndüren basit bir hizmet hayal edebilirsiniz. Bu, kullanıcı başka şeylerle ilgilenmediği sırada, arka planda eşzamansız olarak çalışabilir.

Demomuz için basit bir işlem yapıyoruz. IndexedDB'mizde kaç nesnemiz olduğunu (varsa) görüyoruz. Belirli bir sayının altındaysa sahte kullanıcılar oluştururuz. Aksi takdirde, kaynak kısımla işin bittiği kabul edilir ve demonun otomatik tamamlama bölümünü etkinleştirebiliriz. HandSeed'e 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();
    }
  };
}

İlk satır, birbirine bağlı çok sayıda işlem bulunduğu için biraz karmaşıktır. Şimdi bunları ayrıntılı bir şekilde inceleyelim:

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

Bu, 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 alma.

count()

Tahmin edebileceğiniz gibi sayım yapan count API'sini çalıştırın.

onsuccess = function(e) {

İşlem tamamlandığında bu geri çağırmayı yürütün. Geri çağırma işlevinde, nesne sayısı olan sonuç değerini alabiliriz. Sayı sıfırsa başlangıç noktası işlemimizi başlatırız.

Kullanıcıya, veri almaya başlayacağımız konusunda bir mesaj vermek için daha önce bahsedilen durum div öğesini kullanırız. IndexedDB'nin eşzamansız yapısı nedeniyle, eklemeleri izleyecek basit bir değişken oluşturduk. Sahte kişileri de ekliyoruz. Bu işlevin kaynağı indirmede mevcuttur, ancak işlev şuna benzer 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, verilerimizde arama yapabilmemiz için özel bir gereksinimimiz vardır. IndexedDB, öğeleri büyük/küçük harfe duyarlı olmayacak şekilde aramak için bir yol sağlamaz. Dolayısıyla, soyadı alanının bir kopyasını yeni bir özellik olan arama anahtarı olarak oluştururuz. Hatırlarsanız, verilerimiz için dizin olarak oluşturulması gerektiğini söylediğimiz anahtar budur.

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

Bu, istemciye özel bir değişiklik olduğundan, arka uç sunucusu (ya da bizim örneğimizde sanal arka uç sunucusu) yerine burada yapılır.

Veritabanı eklemelerini etkili bir şekilde gerçekleştirmek için işlemi tüm toplu yazma işlemleri için yeniden kullanmanız gerekir. Her yazma için yeni bir işlem oluşturursanız, tarayıcı her işlem için bir diskin yazılmasına neden olabilir ve bu da çok sayıda öğe eklerken performansınızı çok kötü hale getirir (örneğin "1000 nesne yazmak için 1 dakika" - çok kötü).

Çekirdek tamamlandıktan sonra, uygulamamızın bir sonraki bölümü tetiklenir: setupAutoComplete.

Otomatik Tamamlamayı Oluşturma

Şimdi sıra eğlenceli kısma geldi. jQuery kullanıcı arayüzü Autocomplete eklentisiyle başlayın. jQuery kullanıcı arayüzünün çoğunda olduğu gibi, temel bir HTML öğesiyle başlar ve bunun üzerinde bir oluşturucu yöntemi çağırarak geliştirir. Bu sürecin tamamını, setupAutoComplete adı verilen bir fonksiyonda özetledik. Şimdi bu koda bakalı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 bölümü kaynak mülkün oluşturulmasıdır. jQuery kullanıcı arayüzünün Otomatik Tamamlama denetimi, IndexedDB verilerimiz de dahil olmak üzere olası her türlü ihtiyacı karşılayacak şekilde özelleştirilebilen bir kaynak özellik tanımlamanızı sağlar. API size istek (temel olarak form alanına yazılanlar) ve bir yanıt geri çağırma sağlar. Bu geri çağırma işlemine bir dizi sonucu geri göndermek sizin sorumluluğunuzdadır.

Yapacağımız ilk şey displayÇalışan div öğesini gizlemek. Bu öğe, tek bir çalışanı görüntülemek ve önceden yüklenmişse bunu temizlemek için kullanılır. Artık aramaya başlayabiliriz.

Salt okunur bir işlem, sonuç adı verilen bir dizi ve sonucu otomatik tamamlama denetimine ileten tamamlanmış bir işleyici oluşturarak başlıyoruz.

Girdimizle eşleşen öğeleri bulmak için StackOverflow kullanıcısı Fong-Wan Chau'nun bir ipucundan yararlanalım: Alt uç sınırı olarak girişe ve üst aralık sınırı olarak giriş ile z harfine dayalı bir dizin aralığı kullanırız. Terimi, küçük harfle girdiğimiz veriyle eşleşecek şekilde küçük harfle yazdığımızı da unutmayın.

İşlem tamamlandığında bir imleci açabilir (bir veritabanı sorgusu çalıştırmak gibi düşünebilirsiniz) ve sonuçları tekrarlayabiliriz. jQuery kullanıcı arayüzünün otomatik tamamlama denetimi istediğiniz veri türlerini döndürmenizi sağlar ancak en azından bir değer anahtarı gerektirir. Değeri, adın güzel biçimlendirilmiş bir versiyonu olarak ayarlarız. Ayrıca kişinin tamamını iade ediyoruz. Bunun nedenini birazdan öğreneceksin. İlk olarak, otomatik tamamlama özelliğinin nasıl çalıştığını gösteren bir ekran görüntüsü alalım. jQuery kullanıcı arayüzü için Vader temasını kullanıyoruz.

Tek başına, IndexedDB eşleşmelerinin sonuçlarını otomatik tamamlamayla döndürmek için bu yeterlidir. Bununla birlikte, eşleşme seçildiğinde ilgili eşleşmenin ayrıntılı görünümünü de desteklemek istiyoruz. Otomatik tamamlama oluştururken, önceki Gidon Çubuğu şablonundan yararlanan bir seçme işleyici belirtmiştik.