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