IndexedDB के साथ डेटा बाइंडिंग यूज़र इंटरफ़ेस (यूआई) एलिमेंट

रेमंड कैम्ड
रेमंड कैमडन

शुरुआती जानकारी

IndexedDB, क्लाइंट साइड पर डेटा सेव करने का एक बेहतरीन तरीका है. अगर आपने अभी तक इसे नहीं देखा है, तो हमारा सुझाव है कि आप इस विषय पर काम के एमडीएन ट्यूटोरियल पढ़ें. इस लेख में, एपीआई और इसकी सुविधाओं की कुछ बुनियादी जानकारी मौजूद है. भले ही आपने IndexedDB पहले कभी न देखा हो, लेकिन उम्मीद है कि इस लेख में दिए गए डेमो से आपको पता चलेगा कि इसके साथ क्या किया जा सकता है.

हमारा डेमो, किसी कंपनी के Intranet ऐप्लिकेशन की अवधारणा का आसान सबूत है. ऐप्लिकेशन से कर्मचारी दूसरे कर्मचारियों को खोज पाएंगे. तेज़ और बेहतर अनुभव देने के लिए, कर्मचारी के डेटाबेस को क्लाइंट की मशीन में कॉपी किया जाता है और IndexedDB का इस्तेमाल करके स्टोर किया जाता है. यह डेमो सिर्फ़ किसी एक कर्मचारी के रिकॉर्ड को ऑटोकंप्लीट करने वाली खोज और डिसप्ले की सुविधा देता है. हालांकि, अच्छी बात यह है कि क्लाइंट के पास यह डेटा उपलब्ध होने के बाद, हम इसका इस्तेमाल दूसरे कई तरीकों से भी कर सकते हैं. यहां बताया गया है कि हमारे ऐप्लिकेशन को क्या करना होगा.

  1. हमें IndexedDB के किसी इंस्टेंस को सेट अप और शुरू करना होगा. ज़्यादातर मामलों में यह आसान है, लेकिन इसे Chrome और Firefox दोनों में इस्तेमाल करना थोड़ा मुश्किल साबित होता है.
  2. हमें यह देखना होगा कि हमारे पास कोई डेटा है या नहीं. अगर नहीं है, तो उसे डाउनलोड करना होगा. अब आम तौर पर यह AJAX कॉल से किया जाता था. हमने डेमो के लिए, यूटिलिटी क्लास बनाई है. इससे आसानी से नकली डेटा जनरेट किया जा सकता है. ऐप्लिकेशन को इस बात की पहचान करनी होगी कि वह इस डेटा को कब बना रहा है और उस समय तक उपयोगकर्ता को डेटा का इस्तेमाल करने से रोक देगा. ऐसा एक ही बार करना होता है. जब उपयोगकर्ता अगली बार ऐप्लिकेशन चलाएगा, तब उसे इस प्रोसेस को पूरा करने की ज़रूरत नहीं होगी. ज़्यादा बेहतर डेमो, क्लाइंट और सर्वर के बीच सिंक ऑपरेशन को हैंडल करेगा, लेकिन इस डेमो में यूज़र इंटरफ़ेस (यूआई) के पहलुओं पर ज़्यादा फ़ोकस किया गया है.
  3. ऐप्लिकेशन तैयार होने पर, हम IndexedDB के साथ सिंक होने के लिए, jQuery यूज़र इंटरफ़ेस के ऑटोकंप्लीट कंट्रोल का इस्तेमाल कर सकते हैं. हालांकि, ऑटोकंप्लीट की सुविधा से डेटा का कलेक्शन और बुनियादी सूचियां बनाई जा सकती हैं, लेकिन इसमें किसी भी डेटा सोर्स को अनुमति देने के लिए एक एपीआई मौजूद है. हम दिखाएंगे कि हम अपने IndexedDB डेटा से कनेक्ट करने के लिए इसका इस्तेमाल कैसे कर सकते हैं.

YouTube पर शुरुआत करना

हमारे पास इस डेमो के कई हिस्से हैं. आसानी से शुरुआत करने के लिए, आइए एचटीएमएल वाले हिस्से पर नज़र डालें.

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

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

ज़्यादा जानकारी नहीं है, है न? इस यूज़र इंटरफ़ेस (यूआई) के तीन मुख्य पहलू हमारे लिए अहम हैं. पहला फ़ील्ड "नाम" है, जिसका इस्तेमाल ऑटोकंप्लीट के लिए किया जाएगा. यह लोड होता है बंद है और इसे बाद में JavaScript से चालू किया जाएगा. इसके बाद के स्पैन का इस्तेमाल, शुरुआती सीड के दौरान किया जाता है, ताकि उपयोगकर्ता को अपडेट दिए जा सकें. आखिर में, अपने-आप मिलने वाले सुझाव में से किसी कर्मचारी को चुनने पर, उस आईडी डिसप्ले वाले डिव का इस्तेमाल किया जाएगा.

आइए, अब JavaScript पर एक नज़र डालते हैं. यहां समझने के लिए बहुत सारी बातें हैं, इसलिए हम इसे सिलसिलेवार तरीके से पूरा करेंगे. पूरा कोड आखिर में उपलब्ध होगा, ताकि आप उसे पूरी तरह से देख सकें.

सबसे पहले - IndexedDB के साथ काम करने वाले ब्राउज़र में हमें प्रीफ़िक्स से जुड़ी कुछ समस्याएं आ रही हैं. यहां Mozilla दस्तावेज़ का कुछ कोड दिया गया है. इस कोड में बदलाव करके, हमारे ऐप्लिकेशन के लिए ज़रूरी IndexedDB कॉम्पोनेंट के लिए आसान उपनाम दिया गया है.

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

इसके बाद, हम डेमो के दौरान कुछ ग्लोबल वैरिएबल का इस्तेमाल करेंगे:

var db;
var template;

अब हम jQuery दस्तावेज़ के लिए तैयार ब्लॉक से शुरू करेंगे:

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

हमारा डेमो, कर्मचारी की जानकारी दिखाने के लिए Handlebars.js का इस्तेमाल करता है. हालांकि, बाद में इसका इस्तेमाल नहीं किया जाता. हालांकि, अब हम टेंप्लेट को कंपाइल करके, उसे हटा सकते हैं. हैंडलबार की पहचान करने वाले टाइप के तौर पर, हमने स्क्रिप्ट ब्लॉक को सेट अप कर लिया है. यह बहुत अधिक फ़ैंसी नहीं है, लेकिन गतिशील HTML को प्रदर्शित करना आसान बना देता है.

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

फिर इसे वापस हमारी JavaScript में इस तरह कंपाइल कर दिया जाता है:

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

अब अपने IndexedDB के साथ काम शुरू करते हैं. सबसे पहले - हम इसे खोलते हैं.

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

IndexedDB से किसी कनेक्शन को खोलने पर, हमें डेटा को पढ़ने और उसमें बदलाव करने का ऐक्सेस मिल जाता है. हालांकि, ऐसा करने से पहले हमें यह पक्का करना होता है कि हमारे पास कोईObjectStore हो. ObjectStore एक डेटाबेस टेबल की तरह होता है. एक IndexedDB में कईObjectStore हो सकते हैं, जिनमें से हर एक में संबंधित ऑब्जेक्ट का संग्रह होता है. हमारा डेमो आसान है और इसमें सिर्फ़ एकObjectStore होता है, जिसे हम "कर्मचारी" कहते हैं. जब indexDB पहली बार खोला जाता है या कोड में वर्शन बदला जाता है, तो अपग्रेड की ज़रूरत वाला इवेंट चलाया जाता है. हम इसका इस्तेमाल अपना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();
};

onupgradeneeded इवेंट हैंडलर ब्लॉक में, हम ऑब्जेक्ट स्टोर की सूची ObjectStoreNames की जांच करते हैं, ताकि यह पता लगा सकें कि उसमें कर्मचारी मौजूद है या नहीं. अगर नहीं, तो हम बस ऐसा कर देते हैं. createIndex कॉल महत्वपूर्ण है. हमें IndexedDB को यह बताना होगा कि कुंजियों के अलावा हम किन तरीकों का इस्तेमाल करके डेटा हासिल करेंगे. हम सर्चकी नाम की एक सूची का इस्तेमाल करेंगे. यह बहुत कम में समझाया गया है.

पहली बार स्क्रिप्ट चलाने पर, onungradeneeded इवेंट अपने-आप चलने लगेगा. इसे एक्ज़ीक्यूट करने के बाद या आने वाले समय में चलने वाली प्रोसेस में स्किप करने के बाद, onsuccess हैंडलर चलता है. हमने एक सामान्य (और भद्दा) गड़बड़ी हैंडलर तय किया है और फिर हम handleSeed को कॉल करते हैं.

इसलिए, आगे बढ़ने से पहले, आइए जल्दी से जानते हैं कि यहां क्या हो रहा है. हम डेटाबेस खोलते हैं. हम यह देखने के लिए जांच करते हैं कि हमारा ऑब्जेक्ट स्टोर मौजूद है या नहीं. अगर ऐसा नहीं है, तो हम उसे बनाते हैं. आखिर में, हम हैंडलसीड नाम के फ़ंक्शन को कॉल करते हैं. आइए, अब अपने डेमो के डेटा सीडिंग हिस्से पर ध्यान दें.

मुझे कुछ डेटा दें!

जैसा कि इस लेख की शुरुआत में बताया गया था, यह डेमो एक ऐसे इंट्रानेट स्टाइल वाले ऐप्लिकेशन को फिर से बना रहा है जिसमें सभी जाने-पहचाने कर्मचारियों की एक कॉपी सेव करनी होती है. आम तौर पर, इसमें सर्वर-आधारित एपीआई बनाना शामिल होता है. यह कर्मचारियों की संख्या को वापस कर सकता है. साथ ही, इससे हमें रिकॉर्ड के बैच को वापस लाने का तरीका भी पता चलेगा. आप एक ऐसी सरल सेवा की कल्पना कर सकते हैं जो एक बार में 100 लोगों को वापस लौटा देती है. जब उपयोगकर्ता दूसरे काम कर रहा होता है, तब यह बैकग्राउंड में एसिंक्रोनस तरीके से चल सकता है.

डेमो के लिए, हम कुछ आसान काम करते हैं. हम देखते हैं कि हमारे IndexedDB में कितने ऑब्जेक्ट हैं. अगर तय संख्या से कम है, तो हम सिर्फ़ नकली उपयोगकर्ता बना देंगे. अगर ऐसा नहीं होता है, तो सीड वाला हिस्सा पूरा हो जाता है और हम डेमो के ऑटोकंप्लीट वाले हिस्से को चालू कर सकते हैं. आइए, हैंडलसीड पर नज़र डालते हैं.

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

पहली लाइन थोड़ी जटिल है. इसकी वजह यह है कि कई ऑपरेशंस एक-दूसरे से जुड़े हुए हैं. इसलिए, आइए इसे छोटे-छोटे हिस्सों में बांटते हैं:

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

इससे रीड ओनली ट्रांज़ैक्शन बन जाता है. IndexedDB के साथ सभी डेटा ऑपरेशन को किसी तरह का लेन-देन ज़रूरी होता है.

objectStore("employee");

कर्मचारी ऑब्जेक्ट स्टोर पाएं.

count()

गिनती एपीआई चलाएं - जिसका अनुमान लगाया जा सकता है - गिनती करता है.

onsuccess = function(e) {

इसके बाद - इस कॉलबैक को एक्ज़ीक्यूट करें. कॉलबैक के अंदर, हम नतीजे की वैल्यू पा सकते हैं जो ऑब्जेक्ट की संख्या होती है. अगर संख्या शून्य थी, तो हम सीड प्रोसेस शुरू करते हैं.

हम पहले बताए गए स्थिति div का इस्तेमाल करके उपयोगकर्ता को यह मैसेज देते हैं कि हमें डेटा मिलने वाला है. IndexedDB के एसिंक्रोनस होने की वजह से, हमने एक सिंपल वैरिएबल सेट अप किया है. यह वैरिएबल जोड़े जाने को ट्रैक करेगा. हम फ़र्ज़ी लोगों की पहचान करके, उनके बारे में पता लगाते हैं. उस फ़ंक्शन का सोर्स डाउनलोड में उपलब्ध होता है, लेकिन यह इस तरह का ऑब्जेक्ट दिखाता है:

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

अपने-आप में यह किसी व्यक्ति को परिभाषित करने के लिए काफ़ी है. हालांकि, अपने डेटा को खोजने के लिए, हमें इसकी एक खास ज़रूरत होती है. IndexedDB, आइटम को केस-इनसेंसिटिव तरीके से खोजने का तरीका नहीं दिखाता. इसलिए, हम उपनाम फ़ील्ड की कॉपी को एक नई प्रॉपर्टी यानी searchkey में बनाते हैं. अगर आपको याद है, तो हमारे डेटा के इंडेक्स के तौर पर यही कुंजी बनाई जानी चाहिए.

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

यह एक क्लाइंट के लिए खास तौर पर किया जाने वाला बदलाव है, इसलिए यहां बैक-एंड सर्वर (या हमारे मामले में, काल्पनिक बैक-एंड सर्वर) की जगह पर इसे किया जाता है.

डेटाबेस को बेहतर तरीके से जोड़ने के लिए, आपको बैच में भेजे गए सभी कॉन्टेंट के लेन-देन का फिर से इस्तेमाल करना चाहिए. अगर हर बार लिखने के लिए एक नया ट्रांज़ैक्शन बनाया जाता है, तो ब्राउज़र हर ट्रांज़ैक्शन के लिए डिस्क पर डेटा कॉपी कर सकता है. इससे बहुत सारे आइटम जोड़ने पर आपकी परफ़ॉर्मेंस खराब हो सकती है (जैसे, "1,000 ऑब्जेक्ट लिखने के लिए 1 मिनट" बहुत खराब).

सीड पूरा हो जाने के बाद, हमारे ऐप्लिकेशन का अगला हिस्सा सक्रिय होता है - setupAutoComplete.

ऑटोकंप्लीट की सुविधा सेट अप करना

अब मज़ेदार बात यह है कि - jQuery यूज़र इंटरफ़ेस (यूआई) ऑटोकंप्लीट प्लगिन के साथ काम करें. jQuery यूज़र इंटरफ़ेस (यूआई) की तरह ही, हम बेसिक एचटीएमएल एलिमेंट से शुरू करते हैं और उस पर कंस्ट्रक्टर मेथड को कॉल करके उसे बेहतर बनाते हैं. हमने पूरी प्रक्रिया को एक फ़ंक्शन में हटा दिया है, जिसे सेटअप ऑटोकंप्लीट कहा जाता है. आइए अब उस कोड पर नज़र डालते हैं.

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

}

सोर्स प्रॉपर्टी को बनाना इस कोड का सबसे मुश्किल हिस्सा है. jQuery यूज़र इंटरफ़ेस के ऑटोकंप्लीट कंट्रोल की मदद से, आप ऐसी सोर्स प्रॉपर्टी तय कर सकते हैं जिसे किसी भी संभावित ज़रूरत - यहां तक कि हमारा IndexedDB डेटा भी पूरा करने के लिए पसंद के मुताबिक बनाया जा सकता है. एपीआई आपको अनुरोध (बुनियादी तौर पर, फ़ॉर्म फ़ील्ड में जो टाइप किया गया था) और रिस्पॉन्स कॉलबैक देता है. उस कॉलबैक को कई नतीजे भेजने की ज़िम्मेदारी आपकी है.

सबसे पहले हम डिसप्ले कर्मचारी div को छिपाते हैं. इसका इस्तेमाल किसी कर्मचारी को दिखाने के लिए किया जाता है. साथ ही, अगर किसी कर्मचारी को पहले लोड किया गया है, तो उसे हटाने के लिए इसका इस्तेमाल किया जाता है. अब हम खोजना शुरू कर सकते हैं.

हम रीड-ओनली ट्रांज़ैक्शन, एक अरे जिसे रिज़ल्ट कहते हैं, और एक ऑनकंप्लीट हैंडलर बनाकर शुरुआत करते हैं जो नतीजे को ऑटोकंप्लीट कंट्रोल में पास कर देता है.

हमारे इनपुट से मेल खाने वाले आइटम ढूंढने के लिए, आइए StackOverflow के उपयोगकर्ता फ़ॉन्ग-वान चाउ की एक सलाह का इस्तेमाल करते हैं: हम इनपुट के आधार पर निचली सीमा के तौर पर, इंडेक्स रेंज का इस्तेमाल करते हैं. साथ ही, इनपुट और अक्षर z को ऊपरी रेंज की सीमा के तौर पर इस्तेमाल करते हैं. ध्यान दें, हमने अपने डाले गए अंग्रेज़ी के छोटे अक्षरों से मेल खाने के लिए, शब्द को छोटा कर दिया.

एक बार हो जाने के बाद - हम एक कर्सर खोल सकते हैं (इसे कोई डेटाबेस क्वेरी चलाने जैसा मानें) और नतीजों पर फिर से काम करें. jQuery यूज़र इंटरफ़ेस (यूआई) के ऑटोकंप्लीट कंट्रोल की मदद से, अपनी पसंद का कोई भी डेटा दिखाया जा सकता है. हालांकि, इसके लिए कम से कम वैल्यू कुंजी की ज़रूरत होती है. हम नाम के सही फ़ॉर्मैट वाले वर्शन पर वैल्यू सेट करते हैं. हम पूरे व्यक्ति को वापस भी कर देते हैं. आपको इसकी वजह कुछ सेकंड में दिखेगी. सबसे पहले, यह रहा ऑटोकंप्लीट सुविधा के इस्तेमाल का एक स्क्रीन शॉट. हम jQuery यूज़र इंटरफ़ेस (यूआई) के लिए, वादेर थीम का इस्तेमाल कर रहे हैं.

अपने-आप में, यह हमारे IndexedDB मैच के नतीजों को ऑटोकंप्लीट करने के लिए काफ़ी है. हालांकि, अगर मिलते-जुलते वीडियो को चुना जाता है, तो हम उसकी ज़्यादा जानकारी दिखाने की भी सुविधा देना चाहते हैं. ऑटोकंप्लीट की सुविधा बनाते समय, हमने एक ऐसा सिलेक्टर चुना है जो पहले के हैंडलबार टेंप्लेट का इस्तेमाल करता है.