องค์ประกอบ UI การเชื่อมโยง Databinding ที่มี IndexedDB

เกริ่นนำ

IndexedDB เป็นวิธีที่มีประสิทธิภาพในการจัดเก็บข้อมูลทางฝั่งไคลเอ็นต์ หากคุณยังไม่ได้ดู เราขอแนะนำให้คุณอ่านบทแนะนำ MDN ที่มีประโยชน์เกี่ยวกับหัวข้อนี้ บทความนี้จัดทำขึ้นหรือมีความรู้พื้นฐานเกี่ยวกับ API และฟีเจอร์ แม้คุณจะไม่เคยเห็น IndexedDB มาก่อน แต่หวังว่าการสาธิตในบทความนี้จะทำให้คุณเห็นภาพว่าทำอะไรได้บ้าง

การสาธิตของเราคือการพิสูจน์แนวคิดง่ายๆ ของแอปพลิเคชันอินทราเน็ตสำหรับบริษัท แอปพลิเคชันจะช่วยให้พนักงานสามารถค้นหาพนักงานคนอื่นๆ ได้ ระบบจะคัดลอกฐานข้อมูลพนักงานไปยังเครื่องของลูกค้าและจัดเก็บไว้โดยใช้ IndexedDB เพื่อมอบประสบการณ์การใช้งานที่เร็วและกระชับขึ้น การสาธิตนี้เพียงให้การค้นหาในรูปแบบการเติมข้อความอัตโนมัติและแสดงบันทึกของพนักงาน 1 คน แต่ข้อดีคือ เมื่อมีข้อมูลนี้ในไคลเอ็นต์แล้ว เราจะนำข้อมูลนี้ไปใช้ในทางอื่นๆ ได้อีกหลายวิธีเช่นกัน ต่อไปนี้เป็นข้อมูลสรุปเบื้องต้นว่าแอปพลิเคชันของเราจำเป็นต้องมีอะไรบ้าง

  1. เราต้องตั้งค่าและเริ่มต้นอินสแตนซ์ของ IndexedDB โดยส่วนใหญ่แล้ว ขั้นตอนนี้ไม่มีอะไรซับซ้อน แต่การทำให้ทำงานได้ทั้งใน Chrome และ Firefox เป็นเรื่องยากเล็กน้อย
  2. เราต้องดูว่าเรามีข้อมูลไหม และหากไม่มี ให้ดาวน์โหลด โดยทั่วไปการดำเนินการนี้จะทำผ่านการเรียก AJAX ในการสาธิตนี้ เราได้สร้างคลาสยูทิลิตีแบบง่ายๆ เพื่อสร้างข้อมูลปลอมได้อย่างรวดเร็ว แอปพลิเคชันจะต้องจดจำเมื่อสร้างข้อมูลนี้และป้องกันไม่ให้ผู้ใช้ใช้ข้อมูลนี้จนกว่าจะถึงเวลาดังกล่าว ซึ่งจะเป็นการดำเนินการครั้งเดียว เมื่อผู้ใช้เรียกใช้แอปพลิเคชันในครั้งถัดไป ผู้ใช้ไม่จำเป็นต้องผ่านกระบวนการนี้ การสาธิตขั้นสูงขึ้นจะจัดการกับการซิงค์ระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ แต่การสาธิตนี้จะเน้นที่แง่มุมต่างๆ ของ UI มากกว่า
  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>

ไม่ค่อยเยอะใช่ไหม UI นี้มีประเด็นหลัก 3 ประการที่เราให้ความสำคัญ อย่างแรกคือช่อง "name" ที่จะใช้ในการเติมข้อความอัตโนมัติ ซึ่งการโหลดถูกปิดใช้และจะเปิดใช้งานในภายหลังผ่าน JavaScript ระยะเวลาถัดจากฟีดจะถูกใช้ในระหว่างตั้งต้นเพื่อให้การอัปเดตแก่ผู้ใช้ สุดท้าย ระบบจะใช้ div ที่มีรหัส displayEmployee เมื่อคุณเลือกพนักงานจากการแนะนำอัตโนมัติ

ลองมาดู JavaScript กัน ที่นี่มีหลายสิ่งที่ต้องอธิบาย เราจึงจะค่อยๆ อธิบายไปทีละขั้น คุณจะเห็นโค้ดแบบเต็มที่ตอนท้าย คุณจึงดูได้อย่างละเอียดครบถ้วน

อย่างแรก มีปัญหาเรื่องคำนำหน้าที่เราต้องกังวลในเบราว์เซอร์ที่สนับสนุน IndexedDB ต่อไปนี้เป็นโค้ดบางส่วนจากเอกสารของ Mozilla ที่แก้ไขเพื่อใช้เป็นชื่อแทนง่ายๆ สำหรับคอมโพเนนต์หลักของ IndexedDB ที่แอปพลิเคชันของเราต้องการ

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

ต่อไป ตัวแปรร่วมอีก 2-3 รายการที่เราจะใช้ตลอดการสาธิตมีดังนี้

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 1 รายการอาจมี objectStore หลายรายการ โดยแต่ละรายการจะมีคอลเล็กชันของออบเจ็กต์ที่เกี่ยวข้อง การสาธิตของเราไม่ซับซ้อนและต้องการเพียงออบเจ็กต์เดียวที่เราเรียกว่า "พนักงาน" เมื่อเปิดการจัดทำดัชนีฐานข้อมูลเป็นครั้งแรก หรือเมื่อคุณเปลี่ยนเวอร์ชันในโค้ด จะมีการเรียกใช้เหตุการณ์ 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 จะทำงาน เราได้กำหนดเครื่องจัดการข้อผิดพลาดแบบง่าย (และน่าเกลียด) ไว้ จากนั้นเราจะเรียก handleSeed

ก่อนจะไปต่อ ฉันมาดูสิ่งที่เกิดขึ้นกันก่อน เราเปิดฐานข้อมูล เราตรวจสอบเพื่อดูว่ามีที่เก็บออบเจ็กต์อยู่ไหม หากไม่มี เราจะสร้างให้ ขั้นตอนสุดท้าย เราเรียกฟังก์ชันที่ชื่อว่า ManageSeed ตอนนี้เรามาดูส่วนการใส่ข้อมูลในการสาธิตกัน

ขอข้อมูลหน่อย!

ดังที่กล่าวไว้ในบทนำของบทความนี้ การสาธิตนี้เป็นการสร้างแอปพลิเคชันรูปแบบอินทราเน็ตขึ้นมาใหม่ ซึ่งจำเป็นต้องเก็บสำเนาของพนักงานที่รู้จักทั้งหมด โดยปกติจะต้องสร้าง API แบบเซิร์ฟเวอร์ที่อาจแสดงจำนวนพนักงานอีกครั้ง และช่วยให้เราเรียกข้อมูลชุดระเบียนได้ ลองนึกถึงบริการง่ายๆ ที่รองรับการนับเริ่มต้นและส่งผู้ใช้คืนครั้งละ 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()

เรียกใช้ API จำนวน ซึ่งคุณคาดเดาได้เพื่อดำเนินการนับ

onsuccess = function(e) {

และเมื่อดำเนินการเสร็จแล้ว ให้เรียกใช้โค้ดเรียกกลับนี้ ในโค้ดเรียกกลับ เราจะได้ค่าผลลัพธ์ซึ่งเป็นจำนวนออบเจ็กต์ ถ้าจำนวนเป็น 0 เราจะเริ่มกระบวนการตั้งต้น

เราใช้ 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 รายการ" แย่มาก)

เมื่อซีดทำงานเสร็จแล้ว ส่วนถัดไปของแอปพลิเคชันจะเริ่มทำงาน - SetupAutoComplete

การสร้างการเติมข้อความอัตโนมัติ

มาถึงส่วนที่สนุกแล้วก็ต่อด้วยปลั๊กอินเติมข้อความอัตโนมัติ UI ของ jQuery เช่นเดียวกับ UI ส่วนใหญ่ของ jQuery เราเริ่มต้นด้วยองค์ประกอบ HTML พื้นฐานและปรับปรุงโดยเรียก Method ของตัวสร้าง เราได้สรุปกระบวนการทั้งหมดเป็นฟังก์ชัน setAutoComplete ลองมาดูโค้ดดังกล่าวกัน

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 จะแสดงคำขอ (โดยทั่วไปคือสิ่งที่พิมพ์ลงในช่องแบบฟอร์ม) และโค้ดเรียกกลับในการตอบกลับ คุณมีหน้าที่ส่งอาร์เรย์ของผลลัพธ์กลับไปยังโค้ดเรียกกลับนั้น

สิ่งแรกที่เราทำคือการซ่อน div การแสดงพนักงาน ใช้เพื่อแสดงพนักงานแต่ละคน และหากมีการโหลดพนักงานไว้ก่อนหน้านี้แล้ว ให้ล้างสถานะพนักงานออก ตอนนี้เราก็เริ่มค้นหาได้แล้ว

เราเริ่มต้นโดยการสร้างธุรกรรมแบบอ่านอย่างเดียว อาร์เรย์ที่เรียกว่าผลลัพธ์ และเครื่องจัดการ oncomplete ที่เพียงส่งต่อผลลัพธ์ไปยังการควบคุมการเติมข้อความอัตโนมัติ

ในการค้นหารายการที่ตรงกับข้อมูลที่เราป้อน ให้ใช้เคล็ดลับจากผู้ใช้ StackOverflow อย่าง Fong-Wan Chau: เราใช้ช่วงดัชนีที่อิงกับอินพุตเป็นขอบเขตต่ำสุด และใช้อินพุตบวกตัวอักษร z เป็นขอบเขตของช่วงบน โปรดทราบว่าเราใช้ตัวพิมพ์เล็กในคำให้ตรงกับข้อมูลตัวพิมพ์เล็กที่เราป้อนด้วยเช่นกัน

เมื่อดำเนินการเสร็จแล้ว เราสามารถเปิดเคอร์เซอร์ (ให้คิดว่าเหมือนกับการเรียกใช้การค้นหาฐานข้อมูล) และทำซ้ำตามผลลัพธ์ การควบคุมการเติมข้อความอัตโนมัติของ jQuery UI ช่วยให้คุณสามารถส่งคืนข้อมูลได้ทุกประเภทที่คุณต้องการแต่จำเป็นต้องใช้คีย์ค่าเป็นอย่างน้อย เรากำหนดค่านี้ให้กับชื่อที่มีรูปแบบสวยงาม และเรายังส่งคืนทั้งคนด้วย คุณจะเห็นสาเหตุในอีกสักครู่ ก่อนอื่น นี่คือภาพหน้าจอของการทำงานของการเติมข้อความอัตโนมัติ เรากำลังใช้ธีม Vader สำหรับ jQuery UI

ซึ่งก็เพียงพอที่จะแสดงผลของการจับคู่ IndexedDB ของเราไปยังการเติมข้อความอัตโนมัติ แต่เราก็ต้องการแสดงมุมมองรายละเอียดของการแข่งขันเมื่อเลือกการแข่งขันเหล่านั้น เราระบุเครื่องจัดการที่เลือกเมื่อสร้างการเติมข้อความอัตโนมัติที่ใช้ประโยชน์จากเทมเพลตแฮนเดิลก่อนหน้า