ভূমিকা
IndexedDB হল ক্লায়েন্ট সাইডে ডেটা স্টোর করার একটি শক্তিশালী উপায়। আপনি এখনও এটি না দেখে থাকলে, আমি আপনাকে এই বিষয়ে সহায়ক MDN টিউটোরিয়াল পড়ার জন্য উৎসাহিত করব। এই নিবন্ধে ধরে নেওয়া হয়েছে যে API ও ফিচার সম্পর্কে আপনার প্রাথমিক জ্ঞান আছে। আপনি আগে IndexedDB না দেখে থাকলেও, আশা করি এই নিবন্ধের ডেমো থেকে আপনি বুঝতে পারবেন যে এটি দিয়ে কী কী করা যায়।
আমাদের ডেমো হল কোনও কোম্পানির জন্য এই আইডিয়াটি বাস্তবে ব্যবহার করা যাবে এমন একটি ইন্ট্রানেট অ্যাপ্লিকেশন। অ্যাপ্লিকেশনটি কর্মচারীদের অন্যান্য কর্মচারীদের সার্চ করার অনুমতি দেবে। আরও দ্রুত, ঝটপট অভিজ্ঞতা প্রদান করার জন্য, কর্মচারীর ডেটাবেস ক্লায়েন্টের মেশিনে কপি করা হয় এবং IndexedDB ব্যবহার করে স্টোর করা হয়। ডেমোটি কেবল একটি অটো-কমপ্লিট-স্টাইল সার্চ এবং একজন কর্মচারীর রেকর্ড প্রদর্শন করে, তবে সবচেয়ে ভালো দিকটি হল যে এই ডেটা ক্লায়েন্টের কাছে উপলভ্য হয়ে গেলে, আমরা এটিকে অন্যান্য একাধিক উপায়েও ব্যবহার করতে পারি। আমাদের অ্যাপ্লিকেশনকে কী করতে হবে তার একটি প্রাথমিক রূপরেখা এখানে দেওয়া হল।
- আমাদের IndexedDB-এর একটি ইনস্ট্যান্স সেট-আপ ও ইনিশিয়ালাইজ করতে হবে। বেশিরভাগ ক্ষেত্রে এটি সহজ, তবে Chrome এবং Firefox, দুটিতেই এটি কাজ করানো একটু কঠিন।
- আমাদের কাছে কোনও ডেটা আছে কিনা তা দেখতে হবে এবং না থাকলে তা ডাউনলোড করতে হবে। সাধারণত এটি AJAX কলের মাধ্যমে করা হয়। আমাদের ডেমোর জন্য, আমরা দ্রুত জাল ডেটা তৈরি করতে একটি সাধারণ ইউটিলিটি ক্লাস তৈরি করেছি। অ্যাপ্লিকেশনটিকে এই ডেটা তৈরি করার সময় শনাক্ত করতে হবে এবং ততক্ষণ পর্যন্ত ব্যবহারকারীকে ডেটা ব্যবহার করা থেকে বিরত রাখতে হবে। এটি একবারই করা হয়। পরের বার ব্যবহারকারী অ্যাপ্লিকেশনটি চালালে, তাকে এই প্রসেসের মধ্যে দিয়ে যেতে হবে না। আরও উন্নত ডেমো ক্লায়েন্ট ও সার্ভারের মধ্যে সিঙ্ক অপারেশন ম্যানেজ করবে, তবে এই ডেমো UI-এর উপর বেশি ফোকাস করে।
- অ্যাপ্লিকেশনটি প্রস্তুত হলে, আমরা IndexedDB-এর সাথে সিঙ্ক করার জন্য jQuery UI-এর অটোকমপ্লিট কন্ট্রোল ব্যবহার করতে পারি। অটোমেটিক সম্পূর্ণ করার কন্ট্রোল বেসিক তালিকা ও ডেটার অ্যারে ব্যবহারের অনুমতি দিলেও, যেকোনও ডেটা সোর্স ব্যবহারের অনুমতি দেওয়ার জন্য এতে একটি 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-এর তিনটি প্রধান দিক রয়েছে যেগুলি আমরা গুরুত্ব সহকারে বিবেচনা করি। প্রথমটি হল ফিল্ড, "নাম" যা অটো-কমপ্লিটের জন্য ব্যবহার করা হবে। এটি বন্ধ অবস্থায় লোড হয় এবং পরে জাভাস্ক্রিপ্টের মাধ্যমে চালু করা হবে। ব্যবহারকারীকে আপডেট প্রদান করার জন্য প্রাথমিক সিডের সময় এটির পাশের স্প্যান ব্যবহার করা হয়। অবশেষে, আপনি অটোমেটিক সাজেশন থেকে কোনও কর্মচারী বেছে নিলে displayEmployee আইডি সহ div ব্যবহার করা হবে।
এবার জাভাস্ক্রিপ্ট দেখে নেওয়া যাক। এখানে অনেক কিছু বোঝার আছে, তাই আমরা ধাপে ধাপে এগোব। সম্পূর্ণ কোডটি শেষে উপলভ্য হবে যাতে আপনি এটি সম্পূর্ণভাবে দেখতে পারেন।
প্রথমত - 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(function() {
console.log("Startup...");
...
});
আমাদের ডেমো কর্মচারীর বিবরণ দেখানোর জন্য Handlebars.js ব্যবহার করে। এটি পরে ব্যবহার করা হবে, তবে আমরা এখন আমাদের টেমপ্লেট কম্পাইল করে নিতে পারি। আমরা Handlebars-শনাক্ত করা ধরন হিসেবে একটি স্ক্রিপ্ট ব্লক সেট-আপ করেছি। এটি খুব বেশি আকর্ষণীয় নয়, তবে ডায়নামিক HTML দেখানো সহজ করে তোলে।
<h2>, </h2>
Department: <br/>
Email: <a href='mailto:'></a>
এটি তারপর আমাদের জাভাস্ক্রিপ্টে এইভাবে কম্পাইল করা হয়:
//Create our template
var source = $("#employeeTemplate").html();
template = Handlebars.compile(source);
এখন আমাদের IndexedDB-এর সাথে কাজ শুরু করা যাক। প্রথমে - আমরা এটি খুলি।
var openRequest = indexedDB.open("employees", 1);
IndexedDB-তে কানেকশন খুললে আমরা ডেটা পড়তে ও লিখতে পারি, তবে এটি করার আগে আমাদের নিশ্চিত করতে হবে যে আমাদের কাছে একটি objectStore আছে। objectStore হল ডেটাবেস টেবিলের মতো। একটি IndexedDB-তে একাধিক 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 তৈরি করা অন্তর্ভুক্ত থাকে যা কর্মচারীদের সংখ্যা প্রদান করতে পারে এবং আমাদের জন্য রেকর্ডের ব্যাচগুলি পুনরুদ্ধার করার একটি উপায় প্রদান করতে পারে। আপনি এমন একটি সাধারণ পরিষেবার কথা ভাবতে পারেন যা শুরু থেকে গণনা করতে সাহায্য করে এবং একবারে ১০০ জন ব্যক্তির তথ্য প্রদান করে। ব্যবহারকারী অন্য কাজ করার সময় এটি ব্যাকগ্রাউন্ডে অ্যাসিঙ্ক্রোনাসভাবে চলতে পারে।
আমাদের ডেমোর জন্য, আমরা সহজ কিছু করি। আমাদের 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) {
এবং হয়ে গেলে - এই কলব্যাক এক্সিকিউট করুন। কলব্যাকের মধ্যে আমরা ফলাফল ভ্যালু পেতে পারি যা অবজেক্টের সংখ্যা। সংখ্যাটি শূন্য হলে, আমরা সিড প্রসেস শুরু করি।
আমরা ডেটা পেতে শুরু করতে চলেছি, ব্যবহারকারীকে এই মেসেজ দিতে, আগে উল্লেখ করা স্ট্যাটাস 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();
এটি যেহেতু ক্লায়েন্ট-নির্দিষ্ট পরিবর্তন, তাই এটি ব্যাক-এন্ড সার্ভারের (বা আমাদের ক্ষেত্রে, কাল্পনিক ব্যাক-এন্ড সার্ভার) পরিবর্তে এখানে করা হয়।
ডেটাবেসে আরও ডেটা যোগ করার কাজ যাতে আরও ভালভাবে করা যায়, তার জন্য সব ব্যাচড রাইটের ক্ষেত্রে ট্রানজ্যাকশন আবার ব্যবহার করা উচিত। আপনি প্রতিটি রাইটের জন্য নতুন ট্রানজ্যাকশন তৈরি করলে, ব্রাউজার প্রতিটি ট্রানজ্যাকশনের জন্য ডিস্ক রাইট করতে পারে এবং এর ফলে অনেক আইটেম যোগ করার সময় পারফর্ম্যান্স খুব খারাপ হয়ে যাবে (যেমন, "১০০০ অবজেক্ট রাইট করতে ১ মিনিট"-এর মতো খারাপ)।
সিড করা হয়ে গেলে, আমাদের অ্যাপ্লিকেশনের পরবর্তী অংশটি ফায়ার করা হয় - 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 আপনাকে অনুরোধ (মূলত ফর্ম ফিল্ডে যা টাইপ করা হয়েছে) এবং একটি রেসপন্স কলব্যাক প্রদান করে। সেই কলব্যাকে ফলাফলের অ্যারে ফেরত পাঠানোর দায়িত্ব আপনার।
আমরা প্রথমে displayEmployee div লুকাই। এটি কোনও একজন কর্মীর তথ্য দেখানোর জন্য ব্যবহার করা হয় এবং আগে থেকে কোনও তথ্য লোড করা থাকলে তা মুছে দেয়। এখন আমরা সার্চ করা শুরু করতে পারি।
আমরা শুধু-পড়া-যায় এমন ট্রানজ্যাকশন, result নামের একটি অ্যারে এবং oncomplete হ্যান্ডলার তৈরি করে শুরু করি যা ফলাফলকে অটোকমপ্লিট কন্ট্রোলে পাস করে।
আমাদের ইনপুটের সাথে মেলে এমন আইটেম খুঁজতে, StackOverflow ব্যবহারকারী Fong-Wan Chau-এর দেওয়া একটি পরামর্শ ব্যবহার করা যাক: আমরা ইনপুটের উপর ভিত্তি করে একটি ইন্ডেক্স রেঞ্জ ব্যবহার করি, যেখানে ইনপুটটি নিম্ন সীমা এবং ইনপুটের সাথে 'z' অক্ষর যোগ করে উচ্চ সীমা নির্ধারণ করা হয়। এছাড়াও, মনে রাখবেন যে আমরা যে ছোট হাতের ডেটা লিখেছি তার সাথে ম্যাচ করার জন্য শব্দটিকে ছোট হাতের অক্ষরে লিখেছি।
হয়ে গেলে - আমরা একটি কার্সর খুলতে পারি (ডেটাবেস কোয়েরি চালানোর মতো করে ভাবুন) এবং ফলাফলের উপর ইটারেট করতে পারি। jQuery UI-এর অটোমেটিক সম্পূর্ণ করার কন্ট্রোল আপনাকে যেকোনও ধরনের ডেটা রিটার্ন করার অনুমতি দেয়, তবে ন্যূনতম একটি ভ্যালু কী প্রয়োজন। আমরা নামের একটি সুন্দর ফর্ম্যাট করা ভার্সনে ভ্যালু সেট করি। এছাড়াও, আমরা সম্পূর্ণ ব্যক্তির তথ্য ফিরিয়ে দিই। আপনি এক সেকেন্ডের মধ্যে কারণটি দেখতে পাবেন। প্রথমে, এখানে অ্যাকশনে থাকা অটোকমপ্লিটের একটি স্ক্রিনশট দেওয়া হল। আমরা jQuery UI-এর জন্য Vader থিম ব্যবহার করছি।
এটি নিজেই, অটোকমপ্লিটে আমাদের IndexedDB ম্যাচের ফলাফল দেখানোর জন্য যথেষ্ট। তবে, কোনও ম্যাচ বেছে নেওয়া হলে, আমরা সেটির বিস্তারিত ভিউ দেখানোর সুবিধাও দিতে চাই। অটোকমপ্লিট তৈরি করার সময় আমরা একটি সিলেক্ট হ্যান্ডলার নির্দিষ্ট করেছি যা আগে থেকে হ্যান্ডেলবার টেমপ্লেট ব্যবহার করে।