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

บทนำ

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

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

  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 ด้านที่เราให้ความสำคัญ รายการแรกคือช่อง "ชื่อ" ที่จะใช้สําหรับการเติมข้อความอัตโนมัติ ระบบจะโหลดแบบปิดใช้ และจะเปิดใช้ในภายหลังผ่าน 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 ดังนี้

$(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 จำนวนมาก โดยแต่ละออบเจ็กต์เก็บคอลเล็กชันของออบเจ็กต์ที่เกี่ยวข้อง การสาธิตของเรานั้นเรียบง่ายและต้องใช้ objectStore เพียงรายการเดียวที่เราเรียกว่า “employee” เมื่อมีการเปิด IndexingDB เป็นครั้งแรก หรือเมื่อคุณเปลี่ยนเวอร์ชันในโค้ด เหตุการณ์ onupgradeneed จะถูกเรียกใช้ เราใช้ข้อมูลนี้เพื่อตั้งค่า 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 ซึ่งเป็นอาร์เรย์ของออบเจ็กต์สตอเรจเพื่อดูว่ามี employee หรือไม่ หากไม่ เราก็จะสร้างให้เอง การเรียกใช้ createIndex มีความสำคัญ เราต้องบอก IndexedDB ว่าจะใช้วิธีการใดนอกเหนือจากคีย์ในการดึงข้อมูล เราจะใช้คีย์ที่เรียกว่า "searchkey" เราจะอธิบายเรื่องนี้ในอีกสักครู่

เหตุการณ์ onungradeneeded จะทํางานโดยอัตโนมัติเมื่อเราเรียกใช้สคริปต์ครั้งแรก หลังจากดำเนินการหรือข้ามการทำงานในอนาคต ระบบจะเรียกใช้ตัวแฮนเดิล onsuccess เราได้กำหนดตัวแฮนเดิลข้อผิดพลาดแบบง่าย (และดูไม่สวย) แล้ว จากนั้นจึงเรียก handleSeed

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

Gimme Some Data!

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

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

onsuccess = function(e) {

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

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

เนื่องจากเป็นการแก้ไขเฉพาะไคลเอ็นต์ จึงต้องดำเนินการที่นี่แทนที่จะเป็นในเซิร์ฟเวอร์แบ็กเอนด์ (หรือในกรณีของเราคือเซิร์ฟเวอร์แบ็กเอนด์สมมติ)

หากต้องการเพิ่มฐานข้อมูลอย่างมีประสิทธิภาพ คุณควรใช้ธุรกรรมซ้ำสำหรับการเขียนแบบเป็นกลุ่มทั้งหมด หากคุณสร้างธุรกรรมใหม่สำหรับการเขียนแต่ละรายการ เบราว์เซอร์อาจทำให้เกิดการเขียนดิสก์สำหรับธุรกรรมแต่ละรายการ ซึ่งจะทำให้ประสิทธิภาพแย่มากเมื่อเพิ่มรายการจำนวนมาก (ลองนึกภาพว่า "ใช้เวลา 1 นาทีในการเขียนออบเจ็กต์ 1,000 รายการ" ซึ่งแย่มาก)

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

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

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

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

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

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

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

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