HTML版本5(HTML5)有望在2014年之前达到万维网联盟(W3C)的推荐状态。尽管它不是正式的标准,但网络浏览器供应商正在添加和营销HTML5功能。 HTML5已经在重塑Internet网站和业务线(LOB)应用程序的Web体验。 许多网站,例如Amazon Kindle Cloud Reader,已经在利用HTML5。 HTML5的两个关键功能将极大地改变LOB应用程序:脱机应用程序支持和本地持久存储。 由于HTML5不是官方标准,因此浏览器支持最多是不一致的。
在本文中,了解脱机应用程序支持以及建议HTML5标准提供的各种持久存储功能。 一个示例应用程序有助于说明这些功能。
应用范例
Contact Manager应用程序示例提供了联系人信息(姓名,地址和电话号码)的管理。 它提供了联机模式,脱机模式和简单的数据同步功能,以在切换到联机模式时将本地数据更改同步到服务器。 脱机时,数据驻留在本地持久存储中。 该应用程序在联机和脱机模式下均支持四种基本的持久存储功能-创建,读取,更新和删除(CRUD)。
建筑
图1显示了Contact Manager应用程序体系结构的概述。 服务器体系结构由两个servlet组成:业务服务和数据提供程序。 用户界面(UI)包含一个HTML文件,四个JavaScript模块以及对jQuery库最新版本的外部引用。
图1.应用程序架构概述
数据模型
数据模型由两个数据实体(联系和状态)组成, 如图2所示。 联系人表包含实际联系人数据; 状态表包含状态选择列表的字典值。
图2.数据模型
服务器界面
服务器接口由两个servlet组成: ContactServlet
和DictionaryServlet
。 表1总结了这些servlet。 (servlet的实现以及相应的业务服务和数据提供程序不在本文讨论范围之内。)
表1. servlet的摘要
Servlet名称 | 操作方式 | 参量 | 描述 |
---|---|---|---|
DictionaryServlet 代码> | getstates | 不适用 | 以JavaScript对象符号(JSON)格式返回状态数组。 |
ContactServlet | getallcontacts | 不适用 | 返回JSON格式的联系人记录数组。 |
ContactServlet | delete | contactId 要删除的联系人的ID。 | 删除指定的联系人记录; 返回带有布尔值标志的JSON对象,以指示操作是否成功。 "{"result": true/false"} |
ContactServlet | save |
| 返回带有布尔值标志的JSON对象,以指示操作是否成功以及新的或更新的联系人ID。 "{"contactId": <id>, "result": <true/false>"} |
调用服务器界面
清单1中的代码显示了如何对联系人servlet进行异步调用以检索存储在在线数据库中的联系人。 该代码使用jQuery getJSON
函数来调用联系人servlet。
清单1.从服务器检索联系人
function loadOnlineContacts()
{
$('#contactList').empty();
$('#contactList').append('Loading contact data...');
var url = '/html5app/contact?operation=getallcontacts';
$.getJSON(url, function(data) {
saveOfflineContactData(data);
displayContactData(data);
});
}
清单2中的代码显示了如何将新的或更新的联系人保存到服务器。 它使用jQuery ajax
函数。 该代码使用HTTP POST
将数据发送到联系人servlet。
清单2.将联系人保存到服务器
function postEditedContact(dataString) {
postEditedContact(dataString, false);
}
function postEditedContact(dataString, suppressAlert) {
var contactId = $('input[name="contactId"]')[0].value;
$.ajax({
type: "POST",
url: "/html5app/contact",
data: dataString,
cache: false,
dataType: "json",
success: function(data) {
var result = data.result;
if (result)
{
if (contactId > 0)
{
if (!suppressAlert) {
alert("Contact was successfully updated.");
}
var lastModifyDate = data.lastModifyDate;
$('input[name="lastModifyDate"]')[0].value = lastModifyDate;
}
else
{
if (!suppressAlert) {
alert("Contact was successfully created.");
}
var lastModifyDate = data.lastModifyDate;
$('input[name="lastModifyDate"]')[0].value = lastModifyDate;
$('input[name="contactId"]')[0].value = data.contactId;
}
loadOnlineContacts();
hideEditForm();
}
else
{
alert('An error occurred saving contact ' + contactId + '.');
}
}
});
}
最后一个功能是从在线数据库中删除一条记录。 清单3显示了如何从服务器删除记录。 该代码使用jQuery getJSON
函数来调用联系人servlet。
清单3.删除服务器上的联系人
function deleteOnlineContact(contactId, suppressAlert){
var url = '/html5app/contact?operation=delete&contactId=' + contactId;
$.getJSON(url, function(data) {
var result = data.result;
if (result) {
if (!suppressAlert) {
alert('Contact deleted');
}
loadOnlineContacts();
}
else {
alert('Contact ' + contactId + 'not deleted');
}
});
}
建立本地数据提供者
本地数据提供者在本地保留所有选择列表和联系数据。 HTML5规范包含一些用于持久存储的选项。 选择使用哪种技术取决于您的数据存储和浏览器支持要求。 以下各节讨论了三种持久性存储技术以及使用所有主要Web浏览器支持的持久性存储技术的本地数据提供程序的实现。
HTML5与以下三种持久存储技术相关:
- localStorage -localStorage使用平面键值存储提供了简单的数据存储。 所有主要的Web浏览器,包括Apple®Safari®,Google Chrome™,Microsoft®Windows®InternetExplorer®,Mozilla®Firefox®和Opera™,都支持localStorage。 HTML5 localStorage是同步的,并且是当前唯一支持跨平台和跨浏览器的数据库存储机制。
- WebSQL -WebSQL最初旨在将基于Transact-SQL的数据库引入Web浏览器。 基于与通用关系数据库(例如IBM®DB2®,Microsoft SQLServer®,Oracle®MySQL®Server和Oracle Database)的相似性,学习曲线很低。 某些浏览器(包括Safari,Chrome和Opera)都支持WebSQL。 Firefox或Internet Explorer不支持它。 据推测,由于不再开发WebSQL建议的规范,因此它将被淘汰。
- 索引数据库(Indexed DB) -索引数据库是一个索引层次结构键值存储,类似于许多商业云数据存储产品。 WebSQL不再支持索引数据库,而Firefox,Chrome和将来的Internet Explorer 10当前都支持索引数据库。索引数据库的应用程序编程接口(API)是异步的,并支持索引,查询和事务。 。
本文中的示例解决方案将JSON和localStorage用于持久存储,这主要是因为广泛的浏览器支持localStorage。
本地数据提供商
localStorage方法通过将联系人和字典数据序列化为JSON字符串并将其保存到localStorage来保留联系人和字典数据。 检索数据时,将其反序列化为JSON对象数组并进行相应处理。
本地保存数据
清单4显示了如何将联系人数据保存到localStorage。 JavaScript函数JSON.stringify
用于序列化服务器返回到字符串的JSON数据,以便可以将其存储在localStorage中。
清单4.将数据保存到localStorage
// fetch data from server
...
// convert JSON data to a string
var contactDataString = JSON.stringify(data);
// persist contact data to localstorage
localStorage.setItem("contactData", contactDataString);
本地检索数据
清单5显示了如何从localStorage检索数据。 第一步是从localStorage获取JSON字符串。 然后使用JavaScript函数eval
将字符串转换为JSON对象,该函数将字符串反序列化为JSON对象数组。 使用自定义JavaScript函数displayContactData
显示数据。
清单5.从localStorage读取数据
function loadOfflineContacts()
{
$('#contactList').empty();
$('#contactList').append('Loading contact data...');
var dataStr = localStorage.getItem("contactData");
var data = eval('(' + dataStr + ')');
displayContactData(data);
}
在本地删除记录
清单6显示了如何从localStorage删除记录。
清单6.从localStorage删除一条记录
function deleteOfflineContact(contactId) {
var dataStr = localStorage.getItem("contactData");
var data = eval('(' + dataStr + ')');
var recordUpdated = false;
$.each(data, function(i,item){
if (item.id == contactId) {
item.isDeleted=true;
recordUpdated = true;
return false;
}
});
if (recordUpdated) {
dataStr = JSON.stringify(data);
localStorage.setItem("contactData", dataStr);
alert("Contact was successfully deleted.");
loadOfflineContacts();
}
}
代码:
- 从本地存储读取数据库并反序列化。
- 遍历记录,直到找到
contactId
记录。 - 将
isDeleted
标志设置为true
。 - 在数据同步功能中使用
isDeleted
标志。 (请参阅“ 数据同步 ”部分。) - 将数据持久保存到localStorage,并刷新数据网格。
本地更新和创建记录
清单7显示了如何在localStorage中更新或创建记录。
清单7.更新localStorage中的一条记录
function updateLocalContact() {
var dataStr = localStorage.getItem("contactData");
var data = eval('(' + dataStr + ')');
var contactId = $('input[name="contactId"]')[0].value;
var recordUpdated = false;
if (contactId > 0) {
$.each(data, function(i,item){
if (item.id == contactId) {
item.isDirty=true;
item.firstName = $('input[name="firstName"]')[0].value;
item.lastName = $('input[name="lastName"]')[0].value;
item.street1 = $('input[name="street1"]')[0].value;
item.street2 = $('input[name="street2"]')[0].value;
item.city = $('input[name="city"]')[0].value;
item.state = $('select[name="state"]')[0].value;
item.zipCode = $('input[name="zipCode"]')[0].value;
recordUpdated = true;
return false;
}
});
}
else {
var newContactId = 0;
var nextId = 0;
while(newContactId == 0) {
var found = false;
nextId = nextId - 1;
$.each(data, function(i,item){
if (item.id == nextId) {
found = true;
return false;
}
});
if (!found) {
newContactId = nextId;
}
}
var lastModifyDate = "";
var newContact = {"street2": $('input[name="street2"]')[0].value,
"id":newContactId,
"street1":$('input[name="street1"]')[0].value,
"lastName":$('input[name="lastName"]')[0].value,
"isDirty":true,
"zipCode":$('input[name="zipCode"]')[0].value,
"state":$('select[name="state"]')[0].value,
"lastModifyDate": lastModifyDate,
"isDeleted":false,
"firstName":$('input[name="firstName"]')[0].value,
"city":$('input[name="city"]')[0].value};
var nextIndex = data.length;
data[nextIndex] = newContact;
recordUpdated=true;
}
if (recordUpdated) {
dataStr = JSON.stringify(data);
localStorage.setItem("contactData", dataStr);
alert("Contact was successfully updated.");
}
hideEditForm();
}
如清单7所示,您:
- 从localStorage读取数据库并反序列化。
- 如果要保存的记录的
contactId
不为零(发生更新),则遍历记录直到找到contactId
记录。 然后相应地进行更新。 - 或者,如果记录是新记录(
contactId
不为零),则查找下一个未使用的负contactId
。 - 将其分配给新记录。
- 将新记录追加到数据库。
然后将数据序列化为JSON字符串并保存到localStorage。 服务器同步期间将分配一个有效的(大于零) contactId
。 否定ID是用于将记录标识为新记录的临时ID。
重要的是要知道localStorage:
- 限制为5MB。 (当需要更多数据存储时,应使用索引DB。)
- 所有主要的网络浏览器都支持。
- 仅适用于字符串值。
下一步是使用HTML5构建UI。
使用HTML5构建UI
该示例Contact Manager应用程序具有一个只有一个页面的简单UI。 它支持编辑和删除每个记录,并提供创建新记录的功能。 级联样式表(CSS)和动态HTML(通过jQuery)用于根据需要隐藏和显示创建/编辑子表单。
为了提供一致的用户体验,无论是联机还是脱机都使用同一页面; 唯一的区别是执行操作时将调用哪个数据提供程序。 图3显示了该应用程序。
图3. Contact Manager应用程序
JavaScript模块
该应用程序包含四个自定义JavaScript模块:
- core.js提供了常见JavaScript函数,并由其他模块使用。
- formEvents.js提供了表单和按钮事件处理程序。 它根据联机或脱机状态将数据库操作分派给正确的数据提供者。
- onlinedb.js提供了在线时与服务器通信的功能。
- offlinedb.js提供本地数据存储功能。
所有模块还使用最新版本的jQuery库来遍历数据,发出异步Web请求和动态HTML。 客户端使用JSON与服务器通信。
离线应用程序清单
HTML5脱机功能提供了对静态文件和资源的缓存。 脱机应用程序清单文件(.appcache)是用于为Web应用程序启用脱机应用程序支持的关键文件。 清单文件定义以下信息:
- 离线时哪些资源和页面可用。
- 哪些资源仅在线可用。
- 显示后备页面以显示离线时不可用的资源。
清单文件由三部分组成: CACHE
, NETWORK
和FALLBACK
。 CACHE
下的页面和资源在本地缓存。 NETWORK
下的页面和资源从不缓存,仅在联机时可用。 如果请求的页面脱机不可用,则显示FALLBACK
指定的页面。 NETWORK
部分中的星号( *
)确保所有其他页面和servlet仅在联机时可用。 如果缺少*
,则servlet调用将失败(甚至在线)。 清单8显示了Contact Manager的清单文件。
清单8.离线应用程序清单
CACHE MANIFEST
# Revision 1
CACHE:
default.html
list.html
scripts/core.js
scripts/localdb.js
scripts/onlinedb.js
scripts/formEvents.js
http://code.jquery.com/jquery-1.7.2.min.js
NETWORK:
*
FALLBACK:
/ offline.html
使用脱机应用程序时,重要的是要了解:
- 脱机应用程序清单文件扩展名.appcache必须映射到
text/cache-manifest
多用途Internet邮件扩展名(MIME)类型。 在Apache Tomcat中,通过将mime-mapping
条目添加到服务器的 web.xml文件(而不是Web应用程序的web.xml文件)中来执行此操作。 如果MIME类型不正确,大多数浏览器会默默地忽略脱机应用程序清单。 - 如果存在脱机应用程序清单文件,则始终使用本地缓存的资源(即使在线)。
- 仅当脱机应用程序清单文件发生更改时才更新本地资源,通常是通过更改清单文件中的注释中的修订号来进行的。 在更改应用程序清单文件之前,对HTML或CSS资源的更改不会反映在Web浏览器中。
- 支持离线使用的任何页面都必须具有以下内容:
<html lang="en" manifest="app.appcache">
在线或离线
使用JavaScript,您可以使用navigator.onLine
布尔值检测应用程序是在线还是离线。 如果应用程序在线,则返回True。
表单事件(在线/离线处理)
在联系人管理器中,联机或脱机使用相同的表单。 使该解决方案起作用的关键在于按钮和表单事件处理程序。 检查navigator.onLine
以确定要调用的操作(本地或联机)。 清单9显示了一个联系人数据加载示例。
清单9.加载数据(在HTML BODY的onLoad事件中)
if (navigator.onLine)
{
// selection list needs to be populated prior to synchronizing data
// the list is updated from the online dictionary later
populateOfflineStates();
setStatusText("Synchronizing contact data with server...");
synchronizeContacts();
setStatusText("Loading dictionary data from server...");
populateOnlineStates();
setStatusText("Loading contact data from server...");
loadOnlineContacts();
}
else
{
alert('You are currently offline.');
populateOfflineStates();
setStatusText("Loading contact data from local storage...");
loadOfflineContacts();
}
数据同步
在线时,所有CRUD操作都将servlet用于创建,修改和删除操作。 联机数据库更改时,本地缓存也会更新。
脱机时,所有CRUD操作都使用本地数据提供程序来保留更改。 与服务器重新连接后:
- 本地创建的所有记录都将保留到服务器。
- 在本地修改的所有记录都会在服务器上更新。
- 在本地删除的所有记录都将在服务器上删除。
清单10显示了完整的同步方法。 在同步期间,相同的联机功能用于创建,更新和删除操作。 第一步是使用jQuery $.each
函数遍历本地记录。
使用isDirty
属性标记在本地更新或创建的isDirty
。 如果保存操作的唯一记录ID为负(即未由MySQL数据库分配),则将其标识为新操作。 使用isDeleted
属性标记在本地删除的记录。
清单10.将离线更改同步到服务器
var recordsUpdated = 0;
var recordsCreated = 0;
var recordsDeleted = 0;
$.each(data, function(i,item){
if (item.isDeleted) {
deleteOnlineContact(item.id, true);
recordsDeleted++;
}
else if (item.isDirty && !item.isDeleted) {
$('input[name="contactId"]')[0].value = item.id;
$('input[name="firstName"]')[0].value = item.firstName;
$('input[name="lastName"]')[0].value = item.lastName;
$('input[name="street1"]')[0].value = item.street1;
$('input[name="street2"]')[0].value = item.street2;
$('input[name="city"]')[0].value = item.city;
$('select[name="state"]')[0].value = item.state;
$('input[name="zipCode"]')[0].value = item.zipCode;
var dataString = $("#editContactForm").serialize();
postEditedContact(dataString, true);
if (item.id > 0) {
recordsUpdated++;
}
else {
recordsCreated++;
}
}
});
var msg = "Synchronization Summary\n\tRecords Updated: " + recordsUpdated +
"\n\tRecords Created: " + recordsCreated +"\n\tRecords Deleted: " + recordsDeleted;
alert(msg);
使用getcontacts
操作从数据库中获取最新数据并显示出来。 其他用户所做的任何更改都会反映出来。 然后,数据将在本地保留,以确保离线时可用。
结论
在本文中,示例应用程序展示了一种在线和离线支持的良好模式。 通过将单个HTML页面用于联机和脱机模式,并基于联机/脱机状态调用表单事件处理程序中的相应联机/脱机数据提供程序,从而维护了一致的用户体验。
数据同步算法提供了良好的基础; 它处理同步记录的脱机创建,删除和修改。 但是,它不是可用于生产的代码。 例如,当同一条记录在本地由另一个用户在服务器上修改时,它不会处理冲突。
翻译自: https://www.ibm.com/developerworks/web/library/wa-html5db/index.html