บทนำ
IndexedDB เป็นวิธีที่มีประสิทธิภาพในการจัดเก็บข้อมูลฝั่งไคลเอ็นต์ หากยังไม่ได้ดู เราขอแนะนำให้อ่านบทแนะนำ MDN ที่เป็นประโยชน์เกี่ยวกับหัวข้อนี้ บทความนี้มีสมมติฐานว่าคุณมีความรู้พื้นฐานเกี่ยวกับ API และฟีเจอร์ต่างๆ แม้ว่าคุณจะไม่เคยเห็น IndexedDB มาก่อน แต่หวังว่าเดโมในบทความนี้จะช่วยให้คุณทราบว่าสามารถทำอะไรกับ IndexedDB ได้บ้าง
การสาธิตของเราเป็นการพิสูจน์แนวคิดง่ายๆ เกี่ยวกับแอปพลิเคชันอินทราเน็ตสำหรับบริษัท แอปพลิเคชันนี้จะช่วยให้พนักงานค้นหาพนักงานคนอื่นๆ ได้ ระบบจะคัดลอกฐานข้อมูลพนักงานไปยังเครื่องของไคลเอ็นต์และจัดเก็บโดยใช้ IndexedDB เพื่อให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่รวดเร็วและฉับไวยิ่งขึ้น การสาธิตนี้จะแสดงการค้นหาและแสดงผลระเบียนพนักงานรายการเดียวในรูปแบบการเติมข้อความอัตโนมัติ แต่ข้อดีคือเมื่อข้อมูลนี้พร้อมใช้งานในไคลเอ็นต์แล้ว เราจะนำไปใช้ในรูปแบบอื่นๆ ได้อีกหลายวิธี ต่อไปนี้เป็นโครงร่างพื้นฐานของสิ่งที่แอปพลิเคชันของเราต้องทำ
- เราต้องตั้งค่าและเริ่มต้นอินสแตนซ์ของ IndexedDB ส่วนใหญ่แล้วขั้นตอนนี้จะตรงไปตรงมา แต่การทำให้ใช้งานได้ทั้งใน Chrome และ Firefox อาจยุ่งยากเล็กน้อย
- เราต้องดูว่ามีข้อมูลหรือไม่ และหากไม่มี ให้ดาวน์โหลดข้อมูล โดยปกติแล้ว การดำเนินการนี้จะทำผ่านการเรียก AJAX สำหรับการสาธิตนี้ เราได้สร้างคลาสยูทิลิตีง่ายๆ เพื่อสร้างข้อมูลจำลองอย่างรวดเร็ว แอปพลิเคชันจะต้องรับรู้เมื่อมีการสร้างข้อมูลนี้และป้องกันไม่ให้ผู้ใช้ใช้ข้อมูลจนกว่าจะถึงเวลานั้น ซึ่งจะเป็นการดำเนินการครั้งเดียว เมื่อผู้ใช้เรียกใช้แอปพลิเคชันในครั้งถัดไป ก็ไม่จำเป็นต้องทำตามขั้นตอนนี้อีก การสาธิตขั้นสูงจะจัดการการดำเนินการซิงค์ระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ แต่การสาธิตนี้จะเน้นที่ด้าน UI มากกว่า
- เมื่อแอปพลิเคชันพร้อมแล้ว เราจะใช้การควบคุมการเติมข้อความอัตโนมัติของ 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;
ถัดไปคือตัวแปรส่วนกลางบางส่วนที่เราจะใช้ตลอดการสาธิตนี้
var db;
var template;
ตอนนี้เราจะเริ่มด้วยบล็อก jQuery document ready ดังนี้
$(document).ready(function() {
console.log("Startup...");
...
});
เดโมของเราใช้ Handlebars.js เพื่อแสดงรายละเอียดพนักงาน เราจะยังไม่ใช้เทมเพลตนี้จนกว่าจะถึงขั้นตอนถัดไป แต่เราสามารถคอมไพล์เทมเพลตไว้ก่อนได้เลยเพื่อไม่ให้เสียเวลา เราได้ตั้งค่าบล็อกสคริปต์เป็นประเภทที่ Handlebars รู้จัก แม้จะไม่สวยงามมากนัก แต่ก็ช่วยให้แสดง 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" เมื่อเปิด 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 ซึ่งเป็นอาร์เรย์ของที่เก็บออบเจ็กต์เพื่อดูว่ามี employee หรือไม่ หากไม่ เราจะทำให้เป็นเช่นนั้น การเรียก createIndex มีความสำคัญ เราต้องบอก IndexedDB ว่าเราจะใช้วิธีการใดนอกเหนือจากคีย์เพื่อดึงข้อมูล เราจะใช้คีย์ที่ชื่อว่า searchkey เราจะอธิบายเรื่องนี้ในอีกสักครู่
เหตุการณ์ onungradeneeded จะทำงานโดยอัตโนมัติในครั้งแรกที่เรียกใช้สคริปต์ หลังจากดำเนินการหรือข้ามในการเรียกใช้ในอนาคตแล้ว ระบบจะเรียกใช้แฮนเดิล onsuccess เราได้กำหนดเครื่องมือจัดการข้อผิดพลาดแบบง่ายๆ (และไม่สวยงาม) ไว้ จากนั้นจึงเรียกใช้ handleSeed
ก่อนที่เราจะไปต่อ มาทบทวนกันอย่างรวดเร็วว่าเกิดอะไรขึ้นที่นี่ เราเปิดฐานข้อมูล เราจะตรวจสอบว่ามีที่เก็บออบเจ็กต์อยู่หรือไม่ หากไม่มี เราจะสร้างขึ้นมา สุดท้าย เราจะเรียกใช้ฟังก์ชันชื่อ handleSeed ต่อไปเรามาดูส่วนการป้อนข้อมูลในการสาธิตกัน
ขอข้อมูลหน่อย
ตามที่กล่าวไว้ในบทนำของบทความนี้ การสาธิตนี้เป็นการสร้างแอปพลิเคชันสไตล์อินทราเน็ตขึ้นมาใหม่ซึ่งจำเป็นต้องจัดเก็บสำเนาของพนักงานที่รู้จักทั้งหมด โดยปกติแล้ว การดำเนินการนี้จะเกี่ยวข้องกับการสร้าง 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 การนับ ซึ่งจะทำการนับตามชื่อของ API
onsuccess = function(e) {
และเมื่อเสร็จแล้ว ให้เรียกใช้ Callback นี้ ภายใน Callback เราจะได้รับค่าผลลัพธ์ซึ่งเป็นจำนวนออบเจ็กต์ หากจำนวนเป็น 0 เราจะเริ่มกระบวนการตั้งต้น
เราใช้ div สถานะที่กล่าวถึงก่อนหน้านี้เพื่อส่งข้อความถึงผู้ใช้ว่าเราจะเริ่มรับข้อมูล เนื่องจากลักษณะแบบอะซิงโครนัสของ IndexedDB เราจึงได้ตั้งค่าตัวแปรอย่างง่ายที่ชื่อว่า done ซึ่งจะติดตามการเพิ่ม เราจะวนซ้ำและแทรกบุคคลปลอม แหล่งที่มาของฟังก์ชันดังกล่าวจะพร้อมใช้งานในการดาวน์โหลด แต่จะแสดงผลออบเจ็กต์ที่มีลักษณะดังนี้
{
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 นาทีในการเขียนออบเจ็กต์ 1,000 รายการ" ซึ่งแย่มาก)
เมื่อสร้างข้อมูลตั้งต้นเสร็จแล้ว ส่วนถัดไปของแอปพลิเคชันจะเริ่มทำงาน ซึ่งก็คือ setupAutoComplete
การสร้างการเติมข้อความอัตโนมัติ
ตอนนี้มาถึงส่วนที่สนุกแล้ว นั่นคือการเชื่อมต่อกับปลั๊กอินเติมข้อความอัตโนมัติของ 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 จะให้คำขอ (โดยพื้นฐานแล้วคือสิ่งที่พิมพ์ลงในช่องแบบฟอร์ม) และการเรียกกลับการตอบกลับ คุณมีหน้าที่รับผิดชอบในการส่งอาร์เรย์ของผลลัพธ์กลับไปยัง Callback นั้น
สิ่งแรกที่เราทำคือซ่อน div ของ displayEmployee ซึ่งใช้เพื่อแสดงพนักงานแต่ละคน และหากมีการโหลดพนักงานไว้ก่อนหน้านี้ ก็จะล้างข้อมูลออก ตอนนี้เราเริ่มค้นหาได้แล้ว
เราเริ่มต้นด้วยการสร้างธุรกรรมแบบอ่านอย่างเดียว อาร์เรย์ที่เรียกว่าผลลัพธ์ และเครื่องจัดการ oncomplete ที่ส่งผลลัพธ์ไปยังตัวควบคุมการเติมข้อความอัตโนมัติ
หากต้องการค้นหารายการที่ตรงกับอินพุตของเรา เรามาใช้เคล็ดลับจาก Fong-Wan Chau ผู้ใช้ StackOverflow กัน โดยเราจะใช้ช่วงดัชนีที่อิงตามอินพุตเป็นขอบเขตด้านล่าง และอินพุตบวกกับตัวอักษร z เป็นขอบเขตช่วงด้านบน โปรดทราบว่าเราใช้อักษรตัวพิมพ์เล็กเพื่อให้ตรงกับข้อมูลตัวพิมพ์เล็กที่ป้อน
เมื่อเสร็จแล้ว เราจะเปิดเคอร์เซอร์ (คิดว่าเหมือนกับการเรียกใช้การค้นหาฐานข้อมูล) และทำซ้ำผลลัพธ์ การควบคุมการเติมข้อความอัตโนมัติของ jQuery UI ช่วยให้คุณแสดงผลข้อมูลประเภทใดก็ได้ที่ต้องการ แต่ต้องมีคีย์ค่าเป็นอย่างน้อย เราตั้งค่าเป็นชื่อเวอร์ชันที่จัดรูปแบบอย่างเหมาะสม นอกจากนี้ เรายังแสดงผลลัพธ์เป็นบุคคลทั้งหมดด้วย คุณจะเห็นเหตุผลในอีกสักครู่ ก่อนอื่น นี่คือภาพหน้าจอของการเติมข้อความอัตโนมัติที่ทำงานอยู่ เราใช้ธีม Vader สำหรับ jQuery UI
เพียงเท่านี้ก็เพียงพอที่จะแสดงผลลัพธ์ของ IndexedDB ที่ตรงกันไปยังการเติมข้อความอัตโนมัติ แต่เรายังต้องการรองรับการแสดงมุมมองรายละเอียดของการแข่งขันเมื่อมีการเลือกด้วย เราได้ระบุเครื่องมือจัดการการเลือกเมื่อสร้างการเติมข้อความอัตโนมัติที่ใช้เทมเพลต Handlebars จากก่อนหน้านี้