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

परिचय

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

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

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

शुरू करें

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

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

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

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

अब आइए, 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 ब्लॉक से शुरुआत करेंगे:

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

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

<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 है. ऑब्जेक्टस्टोर, डेटाबेस टेबल की तरह होता है. एक IndexedDB में कई ऑब्जेक्ट स्टोर हो सकते हैं. हर एक में मिलते-जुलते ऑब्जेक्ट का कलेक्शन होता है. हमारा डेमो आसान है और इसमें सिर्फ़ एक objectStore की ज़रूरत होती है, जिसे हम “employee” कहते हैं. जब indexedDB पहली बार खोला जाता है या कोड में वर्शन बदला जाता है, तो onupgradeneeded इवेंट चलता है. हम इसका इस्तेमाल अपने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 को यह बताना होगा कि डेटा को वापस पाने के लिए, हम कुंजियों के अलावा किन तरीकों का इस्तेमाल करेंगे. हम searchkey नाम के एक फ़ंक्शन का इस्तेमाल करेंगे. इस बारे में थोड़ी देर में बताया जाएगा.

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

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

Gimme Some Data!

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

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

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

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

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

objectStore("employee");

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

count()

count API चलाएं, जो अनुमान लगाया जा सकता है कि गिनती करता है.

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 में, छोटे-बड़े अक्षरों के हिसाब से आइटम खोजने का विकल्प नहीं होता. इसलिए, हम lastname फ़ील्ड की कॉपी को नई प्रॉपर्टी, searchkey में बनाते हैं. अगर आपको याद हो, तो हमने कहा था कि यही कुंजी हमारे डेटा के लिए इंडेक्स के रूप में बनाई जानी चाहिए.

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

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

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

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

अपने-आप पूरा होने की सुविधा बनाना

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

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 UI के ऑटोकंप्लीट कंट्रोल की मदद से, एक सोर्स प्रॉपर्टी तय की जा सकती है. इसे किसी भी ज़रूरत के हिसाब से कस्टमाइज़ किया जा सकता है. यहां तक कि, IndexedDB डेटा को भी कस्टमाइज़ किया जा सकता है. एपीआई आपको अनुरोध (फ़ॉर्म फ़ील्ड में टाइप किया गया अनुरोध) और जवाब का कॉलबैक देता है. उस कॉलबैक में नतीजों का कलेक्शन भेजने की ज़िम्मेदारी आपकी है.

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

हम रीड-ओनली ट्रांज़ैक्शन, नतीजा नाम का कलेक्शन, और oncomplete हैंडलर बनाकर शुरू करते हैं. यह हैंडलर, नतीजे को अपने-आप पूरा होने वाले कंट्रोल को पास करता है.

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

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

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