Databinding عناصر UI با IndexedDB

معرفی

IndexedDB یک روش قدرتمند برای ذخیره داده ها در سمت مشتری است. اگر هنوز به آن نگاه نکرده اید، من شما را تشویق می کنم که آموزش های مفید MDN را در این زمینه بخوانید. این مقاله برخی از دانش های اولیه را در مورد API ها و ویژگی ها فرض می کند. حتی اگر قبلا IndexedDB را ندیده اید، امیدواریم نسخه ی نمایشی این مقاله به شما ایده دهد که چه کاری می توان با آن انجام داد.

نسخه ی نمایشی ما اثبات ساده ای از مفهوم برنامه اینترانت برای یک شرکت است. این برنامه به کارمندان اجازه می دهد تا سایر کارمندان را جستجو کنند. به منظور ارائه یک تجربه سریعتر و سریعتر، پایگاه داده کارمندان در دستگاه مشتری کپی شده و با استفاده از IndexedDB ذخیره می شود. نسخه ی نمایشی به سادگی یک جستجو به سبک تکمیل خودکار و نمایش یک سابقه کارمند را ارائه می دهد، اما آنچه خوب است این است که وقتی این داده ها در مشتری در دسترس قرار گرفت، می توانیم از آن به روش های متعدد دیگری نیز استفاده کنیم. در اینجا یک طرح کلی از آنچه برنامه ما باید انجام دهد آورده شده است.

  1. ما باید یک نمونه از IndexedDB را تنظیم و مقداردهی کنیم. در بیشتر موارد این کار ساده است، اما ثابت می‌کند که کارکرد آن در کروم و فایرفاکس کمی مشکل است.
  2. باید ببینیم آیا اطلاعاتی داریم یا نه و اگر نه، آن را دانلود کنیم. اکنون معمولاً این کار از طریق تماس های AJAX انجام می شود. برای نسخه ی نمایشی خود ما یک کلاس کاربردی ساده برای تولید سریع داده های جعلی ایجاد کرده ایم. برنامه باید تشخیص دهد که چه زمانی این داده ها را ایجاد می کند و تا آن زمان از استفاده کاربر از داده ها جلوگیری کند. این یک عملیات یکبار مصرف است. دفعه بعد که کاربر برنامه را اجرا می کند، نیازی به انجام این فرآیند نخواهد داشت. یک نسخه ی نمایشی پیشرفته تر، عملیات همگام سازی بین مشتری و سرور را انجام می دهد، اما این نسخه ی نمایشی بیشتر بر جنبه های رابط کاربری متمرکز است.
  3. وقتی برنامه آماده شد، می توانیم از کنترل تکمیل خودکار jQuery UI برای همگام سازی با IndexedDB استفاده کنیم. در حالی که کنترل تکمیل خودکار لیست ها و آرایه های اولیه داده ها را امکان پذیر می کند، دارای یک API برای اجازه دادن به هر منبع داده است. ما نشان خواهیم داد که چگونه می توانیم از این برای اتصال به داده های IndexedDB خود استفاده کنیم.

شروع شدن

ما بخش‌های متعددی برای این نسخه نمایشی داریم، بنابراین برای شروع ساده، اجازه دهید به بخش HTML نگاه کنیم.

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

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

زیاد نیست، درست است؟ سه جنبه اصلی برای این رابط کاربری وجود دارد که ما به آنها اهمیت می دهیم. اول فیلد "نام" است که برای تکمیل خودکار استفاده خواهد شد. بارگیری آن غیرفعال است و بعداً از طریق جاوا اسکریپت فعال می شود. دهانه کنار آن در مرحله seed اولیه برای ارائه به روز رسانی به کاربر استفاده می شود. در نهایت، زمانی که کارمندی را از پیشنهاد خودکار انتخاب می‌کنید، از div با شناسه displayEmployee استفاده می‌شود.

حالا بیایید نگاهی به جاوا اسکریپت بیندازیم. در اینجا چیزهای زیادی برای هضم وجود دارد، بنابراین ما آن را گام به گام پیش خواهیم برد. کد کامل در پایان در دسترس خواهد بود، بنابراین می توانید آن را به طور کامل مشاهده کنید.

اول از همه - در میان مرورگرهایی که از IndexedDB پشتیبانی می کنند، باید نگران برخی از مشکلات پیشوند باشیم. در اینجا تعدادی کد از مستندات موزیلا وجود دارد که برای ارائه نام مستعار ساده برای اجزای اصلی 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 برای نمایش جزئیات کارمند استفاده می کند. این تا بعداً مورد استفاده قرار نمی‌گیرد، اما می‌توانیم همین الان الگوی خود را کامپایل کرده و آن را از سر راه برداریم. ما یک بلوک اسکریپت را به عنوان یک نوع شناسایی شده توسط Handlebars تنظیم کرده ایم. این خیلی شیک نیست، اما نمایش HTML پویا را آسان‌تر می‌کند.

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

سپس در جاوا اسکریپت ما به این صورت کامپایل می شود:

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

حالا بیایید کار با IndexedDB خود را شروع کنیم. اول - ما آن را باز می کنیم.

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

باز کردن یک اتصال به IndexedDB به ما دسترسی به خواندن و نوشتن داده ها را می دهد، اما قبل از انجام این کار، باید از داشتن یک ObjectStore اطمینان حاصل کنیم. ObjectStore مانند یک جدول پایگاه داده است. یک IndexedDB ممکن است دارای چندین شی فروشگاه باشد که هر کدام مجموعه ای از اشیاء مرتبط را در خود جای می دهند. نسخه ی نمایشی ما ساده است و فقط به یک objectStore نیاز دارد که آن را "کارمند" می نامیم. هنگامی که 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 بگوییم که خارج از کلیدها از چه روش هایی برای بازیابی داده ها استفاده خواهیم کرد. ما از یکی به نام کلید جستجو استفاده خواهیم کرد. در این مورد کمی توضیح داده شده است.

اولین باری که اسکریپت را اجرا می کنیم رویداد onungradeneeded به طور خودکار اجرا می شود. پس از اجرا، یا نادیده گرفتن آن در اجرای آینده، onsuccess handler اجرا می شود. ما یک کنترل کننده خطای ساده (و زشت) تعریف کرده ایم و سپس handleSeed فراخوانی می کنیم.

بنابراین قبل از ادامه، اجازه دهید به سرعت آنچه را که در اینجا می‌گذرد مرور کنیم. پایگاه داده را باز می کنیم. ما بررسی می کنیم که آیا ذخیره شی ما وجود دارد یا خیر. اگر نشد، آن را ایجاد می کنیم. در نهایت تابعی به نام handleSeed را فراخوانی می کنیم. اکنون بیایید توجه خود را به بخش کاشت اطلاعات نسخه ی نمایشی خود معطوف کنیم.

داده ها را بدهید!

همانطور که در مقدمه این مقاله ذکر شد، این نسخه آزمایشی یک برنامه کاربردی به سبک اینترانت را بازسازی می کند که نیاز به ذخیره یک کپی از همه کارمندان شناخته شده دارد. معمولاً این شامل ایجاد یک API مبتنی بر سرور است که می‌تواند تعدادی از کارمندان را برگرداند و راهی برای بازیابی دسته‌ای از رکوردها برای ما فراهم کند. شما می توانید یک سرویس ساده را تصور کنید که از تعداد شروع پشتیبانی می کند و 100 نفر را در یک زمان برمی گرداند. این می‌تواند به‌صورت ناهمزمان در پس‌زمینه اجرا شود، در حالی که کاربر در حال انجام کارهای دیگر نیست.

برای نسخه ی نمایشی خود، ما یک کار ساده انجام می دهیم. ما می بینیم که در IndexedDB خود، در صورت وجود، چند شی داریم. اگر کمتر از یک عدد مشخص باشد، ما به سادگی کاربران جعلی ایجاد خواهیم کرد. در غیر این صورت در نظر گرفته می‌شویم که با قسمت seed تمام شده است و می‌توانیم قسمت تکمیل خودکار نسخه آزمایشی را فعال کنیم. بیایید به 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) {

و پس از اتمام - این callback را اجرا کنید. در داخل callback می توانیم مقدار نتیجه را که تعداد اشیاء است بدست آوریم. اگر شمارش صفر بود، فرآیند بذر خود را شروع می کنیم.

ما از وضعیت 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 راهی برای جستجوی اقلام به روشی که به حروف بزرگ و کوچک حساس نیست ارائه نمی دهد. بنابراین، ما یک کپی از قسمت نام خانوادگی را در یک ویژگی جدید، کلید جستجو ایجاد می کنیم. اگر به خاطر داشته باشید، این کلیدی است که گفتیم باید به عنوان شاخصی برای داده های ما ایجاد شود.

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

از آنجایی که این یک اصلاح مخصوص کلاینت است، در اینجا برخلاف سرور بک‌اند (یا در مورد ما، سرور بک‌اند خیالی) انجام می‌شود.

برای انجام اضافات پایگاه داده به شیوه ای کارآمد، باید از تراکنش برای همه نوشته های دسته ای استفاده مجدد کنید. اگر برای هر نوشتن یک تراکنش جدید ایجاد کنید، مرورگر ممکن است باعث نوشتن دیسک برای هر تراکنش شود، و این باعث می‌شود عملکرد شما در هنگام اضافه کردن موارد بسیار وحشتناک شود (فکر کنید "1 دقیقه برای نوشتن 1000 شی" - وحشتناک است).

پس از انجام seed، بخش بعدی برنامه ما فعال می شود - setupAutoComplete.

ایجاد تکمیل خودکار

اکنون برای بخش سرگرم کننده - اتصال با افزونه jQuery UI Autocomplete . مانند بسیاری از jQuery UI، ما با یک عنصر اولیه HTML شروع می کنیم و با فراخوانی یک متد سازنده روی آن، آن را تقویت می کنیم. ما کل فرآیند را در تابعی به نام 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 ما - سفارشی شود. API درخواست (در اصل آنچه در قسمت فرم تایپ شده است) و یک پاسخ پاسخ به شما ارائه می دهد. شما مسئول ارسال آرایه ای از نتایج به آن تماس برگشتی هستید.

اولین کاری که انجام می دهیم این است که displayEmployee div را مخفی می کنیم. این برای نمایش یک کارمند و اگر قبلاً بارگذاری شده باشد، برای پاک کردن آن استفاده می شود. اکنون می توانیم جستجو را شروع کنیم.

ما با ایجاد یک تراکنش فقط خواندنی، یک آرایه به نام نتیجه و یک کنترل کننده ناکامل شروع می کنیم که به سادگی نتیجه را به کنترل تکمیل خودکار ارسال می کند.

برای یافتن مواردی که با ورودی ما مطابقت دارند، اجازه دهید از یک نکته توسط کاربر StackOverflow Fong-Wan Chau استفاده کنیم: ما از یک محدوده شاخص بر اساس ورودی به عنوان مرز انتهایی پایین و ورودی به اضافه حرف z به عنوان مرز محدوده بالایی استفاده می کنیم. . همچنین توجه داشته باشید که عبارت را کوچک می کنیم تا با داده های کوچکی که وارد کرده ایم مطابقت داشته باشد.

پس از اتمام - می توانیم مکان نما را باز کنیم (مثل اجرای یک پرس و جو پایگاه داده در نظر بگیرید) و روی نتایج تکرار کنیم. کنترل تکمیل خودکار jQuery UI به شما امکان می دهد هر نوع داده ای را که می خواهید برگردانید، اما حداقل به یک کلید مقدار نیاز دارد. ما مقدار را روی یک نسخه با فرمت زیبا از نام تنظیم کردیم. ما همچنین کل فرد را برمی گردانیم. در یک ثانیه خواهید دید چرا ابتدا، در اینجا یک اسکرین شات از تکمیل خودکار در حال انجام است. ما از تم Vader برای jQuery UI استفاده می کنیم.

این به خودی خود برای بازگرداندن نتایج مطابقت های IndexedDB ما به تکمیل خودکار کافی است. اما ما همچنین می‌خواهیم از نمایش نمای جزئیات مسابقه در هنگام انتخاب پشتیبانی کنیم. هنگام ایجاد تکمیل خودکار که از الگوی Handlebars قبلی استفاده می‌کند، یک کنترل‌کننده انتخاب مشخص کردیم.