本例使用indexedDB API设计一个电子刊物发布的应用,演示效果如图1所示。在示例页面中,显示3个表单框,第一个表单框登录电子刊物信息,并保存在indexedDB数据库中。第二个表单框用于管理数据库中的记录,可以根据需要清空全部记录,或者删除指定的记录。第三个表单框用于显示数据库中所有的电子刊物记录。
图1 电子刊物发布应用效果
【操作步骤】
第1步,设计HTML结构。整个页面包含2部分:第一部分是3个表单,分别用于实现信息录入、信息删除和信息显示;第2部分是4个包含框,其中顶部包含框用于操作提示,底部3个包含框用于显示记录信息。
- 第1个表单框:用于信息录入。
<form id="register-form"> <table><tbody><tr> <td><label for="pub-title" class="required">书名: </label></td> <td><input type="text" id="pub-title" name="pub-title" /></td> </tr><tr> <td><label for="pub-biblioid" class="required"> 出版编号ID:<br/> <span class="note">(ISBN, ISSN, etc.)</span> </label></td> <td><input type="text" id="pub-biblioid" name="pub-biblioid"/></td> </tr> <tr> <td><label for="pub-year">出版日期(年份): </label></td> <td><input type="number" id="pub-year" name="pub-year" /></td> </tr> </tbody><tbody><tr> <td><label for="pub-file">封面图片: </label></td> <td><input type="file" id="pub-file"/></td> </tr><tr> <td><label for="pub-file-url">封面图片在线URL:<br/> <span class="note">(同源URL)</span> </label></td> <td><input type="text" id="pub-file-url" name="pub-file-url"/></td> </tr> </tbody> </table> <div class="button-pane"> <input type="button" id="add-button" value="添加发布" /> <input type="reset" id="register-form-reset"/> </div> </form>
- 第2个表单框:用于记录删除。
<form id="delete-form"> <table><tbody> <tr> <td><label for="pub-biblioid-to-delete">出版编号ID:<br/> <span class="note">(ISBN, ISSN, etc.)</span> </label></td> <td><input type="text" id="pub-biblioid-to-delete" name="pub-biblioid-to-delete" /></td> </tr><tr> <td><label for="key-to-delete">索引:<br/> <span class="note">(如1、2、3等)</span> </label></td> <td><input type="text" id="key-to-delete" name="key-to-delete" /></td> </tr> </tbody> </table> <div class="button-pane"> <input type="button" id="delete-button" value="删除发布" /> <input type="button" id="clear-store-button" value="清空所有发布信息" class="destructive" /> </div> </form>
- 第3个表单框:用于显示记录。
<form id="search-form"> <div class="button-pane"> <input type="button" id="search-list-button" value="显示数据库中所有发布信息" /> </div> </form>
- 顶部的操作提示提示包含框。
<h1>电子出版物仓储</h1> <div class="note"> <p>浏览器支持: </p> <div id="compat"> </div> </div> <div id="msg"> </div>
- 底部包含3个包含框,用于显示记录信息。
<div> <div id="pub-msg"> </div> <div id="pub-viewer"> </div> <ul id="pub-list"> </ul> </div>
第2步,设计Javascript脚本部分。把所有Javascript代码都放在一个函数表达式中,并进行调用,这样做的目的是定义一个独立的作用域,把其中所有的变量与外界代码隔绝起来。首先,初始化所有变量,并重置UI标签。
(function () {
var COMPAT_ENVS = [
['Firefox', ">= 16.0"],
['Google Chrome',
">= 24.0 (可能需谷歌浏览器),没有BLOB存储支持"]
];
var compat = $('#compat');
compat.empty();
compat.append('<ul id="compat-list"></ul>');
COMPAT_ENVS.forEach(function(val, idx, array) {
$('#compat-list').append('<li>' + val[0] + ': ' + val[1] + '</li>');
});
const DB_NAME = 'mdn-demo-indexeddb-epublications';
const DB_VERSION = 1; // 使用long long型值(不要使用float值)
const DB_STORE_NAME = 'publications';
var db;
// 用来跟踪哪些视图显示,避免无用的加载它
var current_view_pub_key;
})(); // 执行函数表达式 (IIFE)
第3步,创建本地数据库,设计数据表,定义字段类型。
function openDb() {
console.log("openDb ...");
var req = indexedDB.open(DB_NAME, DB_VERSION);
req.onsuccess = function (evt) {
// 更好地使用this比req得到的结果好,以避免垃圾回收问题
// db = req.result;
db = this.result;
console.log("openDb DONE");
};
req.onerror = function (evt) {
console.error("openDb:", evt.target.errorCode);
};
req.onupgradeneeded = function (evt) {
console.log("openDb.onupgradeneeded");
var store = evt.currentTarget.result.createObjectStore(
DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
store.createIndex('biblioid', 'biblioid', { unique: true });
store.createIndex('title', 'title', { unique: false });
store.createIndex('year', 'year', { unique: false });
};
}
第4步,定义数据库操作函数。getObjectStore()用于读取匹配的记录,clearObjectStore()用于清除数据库中记录表,getBlob()能够根据键值找到本地数据库中存储的附件文件,displayPubList()函数能够从本地数据库中查询所有记录,并显示在页面中。
//* @参数 {string} store_name
//* @参数 {string} mode 可以是"readonly"或"readwrite"
function getObjectStore(store_name, mode) {
var tx = db.transaction(store_name, mode);
return tx.objectStore(store_name);
}
function clearObjectStore(store_name) {
var store = getObjectStore(DB_STORE_NAME, 'readwrite');
var req = store.clear();
req.onsuccess = function(evt) {
displayActionSuccess("Store cleared");
displayPubList(store);
};
req.onerror = function (evt) {
console.error("clearObjectStore:", evt.target.errorCode);
displayActionFailure(this.error);
};
}
function getBlob(key, store, success_callback) {
var req = store.get(key);
req.onsuccess = function(evt) {
var value = evt.target.result;
if (value) success_callback(value.blob);
};
}
// * @参数 {IDBObjectStore=} store
function displayPubList(store) {
console.log("displayPubList");
if (typeof store == 'undefined')
store = getObjectStore(DB_STORE_NAME, 'readonly');
var pub_msg = $('#pub-msg');
pub_msg.empty();
var pub_list = $('#pub-list');
pub_list.empty();
// 重置iframe,不显示以前的内容
newViewerFrame();
var req;
req = store.count();
req.onsuccess = function(evt) {
pub_msg.append('<p>在对象仓库中共计有 <strong>' + evt.target.result + '</strong> 记录。</p>');
};
req.onerror = function(evt) {
console.error("add error", this.error);
displayActionFailure(this.error);
};
var i = 0;
req = store.openCursor();
req.onsuccess = function(evt) {
var cursor = evt.target.result;
// 如果游标指向某个位置,则访问该数据.
if (cursor) {
console.log("displayPubList cursor:", cursor);
req = store.get(cursor.key);
req.onsuccess = function (evt) {
var value = evt.target.result;
var list_item = $('<li>' +
'[' + cursor.key + '] ' +
'(ID:' + value.biblioid + ') ' +
value.title +
'</li>');
if (value.year != null)
list_item.append(' - ' + value.year);
if (value.hasOwnProperty('blob') &&
typeof value.blob != 'undefined') {
var link = $('<a href="' + cursor.key + '">附件</a>');
link.on('click', function() { return false; });
link.on('mouseenter', function(evt) {
setInViewer(evt.target.getAttribute('href')); });
list_item.append(' / ');
list_item.append(link);
} else {
list_item.append(" / 没有附件");
}
pub_list.append(list_item);
};
// 移动到存储中的下一个对象
cursor.continue();
// 此计数器仅用于创建不同的id。
i++;
} else {
console.log("没有更多的条目");
}
};
}
第5步,定义视图操作函数。newViewerFrame()函数用于在<div id="pub-viewer">包含框中嵌入一个浮动框架,以便显示附件文件信息;setInViewer()函数根据键值,把对应附件文件中图片以HTML字符串形式在浮动框架中显示。
function newViewerFrame() {
var viewer = $('#pub-viewer');
viewer.empty();
var iframe = $('<iframe />');
viewer.append(iframe);
return iframe;
}
function setInViewer(key) {
console.log("setInViewer:", arguments);
key = Number(key);
if (key == current_view_pub_key)
return;
current_view_pub_key = key;
var store = getObjectStore(DB_STORE_NAME, 'readonly');
getBlob(key, store, function(blob) {
console.log("setInViewer blob:", blob);
var iframe = newViewerFrame();
if (blob.type == 'text/html') {
var reader = new FileReader();
reader.onload = (function(evt) {
var html = evt.target.result;
iframe.load(function() {
$(this).contents().find('html').html(html);
});
});
reader.readAsText(blob);
} else if (blob.type.indexOf('image/') == 0) {
iframe.load(function() {
var img_id = 'image-' + key;
var img = $('<img id="' + img_id + '"/>');
$(this).contents().find('body').html(img);
var obj_url = window.URL.createObjectURL(blob);
$(this).contents().find('#' + img_id).attr('src', obj_url);
window.URL.revokeObjectURL(obj_url);
});
} else if (blob.type == 'application/pdf') {
$('*').css('cursor', 'wait');
var obj_url = window.URL.createObjectURL(blob);
iframe.load(function() {
$('*').css('cursor', 'auto');
});
iframe.attr('src', obj_url);
window.URL.revokeObjectURL(obj_url);
} else {
iframe.load(function() {
$(this).contents().find('body').html("没有查看可用");
});
}
});
}
第6步,向数据库中添加记录。其中addPublicationFromUrl()函数根据在线URL,把电子出版物的相关字段信息添加到本地数据库中;addPublication()函数根据用户选择的附件文件,把附件文件以Blob对象的形式保存到本地数据库中。
//* @参数 {string} biblioid
//* @参数 {string} title
//* @参数 {number} year
//* @参数 {string} url 图像下载的URL,存储在本地IndexedDB数据库
function addPublicationFromUrl(biblioid, title, year, url) {
console.log("addPublicationFromUrl:", arguments);
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function (evt) {
if (xhr.status == 200) {
console.log("Blob恢复");
var blob = xhr.response;
console.log("Blob:", blob);
addPublication(biblioid, title, year, blob);
} else {
console.error("addPublicationFromUrl error:",
xhr.responseText, xhr.status);
}
};
xhr.send();
}
//* @参数 {string} biblioid
//* @参数 {string} title
//* @参数 {number} year
//* @参数 {Blob=} blob
function addPublication(biblioid, title, year, blob) {
console.log("添加发布的参数:", arguments);
var obj = { biblioid: biblioid, title: title, year: year };
if (typeof blob != 'undefined')
obj.blob = blob;
var store = getObjectStore(DB_STORE_NAME, 'readwrite');
var req;
try {
req = store.add(obj);
} catch (e) {
if (e.name == 'DataCloneError')
displayActionFailure("这个引擎不知道如何克隆一个Blob, " + "使用Firefox");
throw e;
}
req.onsuccess = function (evt) {
console.log("添加成功");
displayActionSuccess();
displayPubList(store);
};
req.onerror = function() {
console.error("addPublication error", this.error);
displayActionFailure(this.error);
};
}
第7步,删除数据库中记录。其中deletePublicationFromBib()函数能够根据id信息删除指定的记录,deletePublication()能够根据键值删除指定记录。
//* @参数 {string} biblioid
function deletePublicationFromBib(biblioid) {
console.log("deletePublication:", arguments);
var store = getObjectStore(DB_STORE_NAME, 'readwrite');
var req = store.index('biblioid');
req.get(biblioid).onsuccess = function(evt) {
if (typeof evt.target.result == 'undefined') {
displayActionFailure("没有匹配的记录");
return;
}
deletePublication(evt.target.result.id, store);
};
req.onerror = function (evt) {
console.error("deletePublicationFromBib:", evt.target.errorCode);
};
}
//* @参数 {number} key
//* @参数 {IDBObjectStore=} store
function deletePublication(key, store) {
console.log("deletePublication:", arguments);
if (typeof store == 'undefined')
store = getObjectStore(DB_STORE_NAME, 'readwrite');
var req = store.get(key);
req.onsuccess = function(evt) {
var record = evt.target.result;
console.log("记录:", record);
if (typeof record == 'undefined') {
displayActionFailure("没有匹配的记录");
return;
}
req = store.delete(key);
req.onsuccess = function(evt) {
console.log("evt:", evt);
console.log("evt.target:", evt.target);
console.log("evt.target.result:", evt.target.result);
console.log("删除成功");
displayActionSuccess("删除成功");
displayPubList(store);
};
req.onerror = function (evt) {
console.error("删除发布:", evt.target.errorCode);
};
};
req.onerror = function (evt) {
console.error("删除发布:", evt.target.errorCode);
};
}
第8步,定义各种提示信息函数。
function displayActionSuccess(msg) {
msg = typeof msg != 'undefined' ? "Success: " + msg : "成功";
$('#msg').html('<span class="action-success">' + msg + '</span>');
}
function displayActionFailure(msg) {
msg = typeof msg != 'undefined' ? "Failure: " + msg : "失败";
$('#msg').html('<span class="action-failure">' + msg + '</span>');
}
function resetActionStatus() {
console.log("更新状态中 ...");
$('#msg').empty();
console.log("已完成更新");
}
第9步,定义事件监听函数,主要是根据用户单击的按钮,分别调用对应的操作函数。
function addEventListeners() {
console.log("addEventListeners");
$('#register-form-reset').click(function(evt) {
resetActionStatus();
});
$('#add-button').click(function(evt) {
console.log("添加中 ...");
var title = $('#pub-title').val();
var biblioid = $('#pub-biblioid').val();
if (!title || !biblioid) {
displayActionFailure("所需字段丢失");
return;
}
var year = $('#pub-year').val();
if (year != '') {
//如果引擎支持EcmaScript 6,最好使用Number.isInteger
if (isNaN(year)) {
displayActionFailure("Invalid year");
return;
}
year = Number(year);
} else {
year = null;
}
var file_input = $('#pub-file');
var selected_file = file_input.get(0).files[0];
console.log("选定的文件:", selected_file);
var file_url = $('#pub-file-url').val();
if (selected_file) {
addPublication(biblioid, title, year, selected_file);
} else if (file_url) {
addPublicationFromUrl(biblioid, title, year, file_url);
} else {
addPublication(biblioid, title, year);
}
});
$('#delete-button').click(function(evt) {
console.log("删除中 ...");
var biblioid = $('#pub-biblioid-to-delete').val();
var key = $('#key-to-delete').val();
if (biblioid != '') {
deletePublicationFromBib(biblioid);
} else if (key != '') {
// 如果引擎支持EcmaScript 6,最好使用Number.isInteger
if (key == '' || isNaN(key)) {
displayActionFailure("非法的key");
return;
}
key = Number(key);
deletePublication(key);
}
});
$('#clear-store-button').click(function(evt) {
clearObjectStore();
});
var search_button = $('#search-list-button');
search_button.click(function(evt) {
displayPubList();
});
}
第10步,页面初始化操作。当页面加载完成之后,调用openDb()函数创建数据库,调用addEventListeners()函数开始监听各个按钮的操作。
openDb(); addEventListeners();