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