您的位置:首页 > 数据库

HTML5本地存储之Web SQL+LocalStorage+ApplicationCache以及indexed+LocalStorage+ApplicationCache构建的离线应用实例代码

2015-11-15 14:31 856 查看
Application Cache:

(1)manifest文件有变化才会更新,或者清除缓存,或者程序干预applicationCache.update+applicationCache.swapCache。同时applicationCache有一个status属性

(2)一次必须更新manifest中所有的文件。(如果要更新10个文件,但是这次只完成了9个那么更新是失败的)

(3)这次的更新下次打开浏览器才会生效,除非程序干预

(4)主要状态:checking为浏览器查找更新时候触发;error在检查更新或者下载资源期间发生错误时候触发;noupdate在检查描述文件发现文件没有变化时候触发;downloading在开始下载应用缓存资源的时候开始触发;progress在文件下载应用缓存的时候持续不断的触发;updateReady在页面新的应用缓存下载完毕并且可以通过swapCache使用时触发,swapCache表示启用新的应用缓存;cached在应用缓存完整可用时触发.

通过在控制台输入applicationCache.update()就可以手动干预,让应用缓存为检查更新而触发上述事件

如果触发了updateReady那么表示说明新版本的应用缓存已经可用,这时候可以调用swapCache启用新的缓存

EventUtil.addHandler(applicationCache,"updateReady",function()
{
applicationCache.swapCache();
})


Web SQL:

(1)核心方法有openDatabase和transaction,executeSql

LocalStorage:

(1)只能用于字符串和数值存储不能保存文件

(2)不能给localStorage指定任何访问规则,要访问同一个localStorage页面必须来自于同一个域,子域名无效,同时使用同一种协议,在同一个端口号上。

(3)localStorage保存数据到通过javascript删除或者用户清除浏览器缓存为止

(4)对storage任何修改都会在文档上触发storage事件,当通过属性或者setItem保存数据,通过delete或者removeItem删除数据或者调用clear方法,都会触发该事件。该event对象有domian,key,newValue,oldValue属性。IE8和FF只实现了domain属性。不论对sessionStorage,localStorage,globalStorage操作都触发storage事件!

主页只能用application Cache,同时jQuery.js也用了application Cache,因为该文件在离线的时候也必须能够使用,最后的manifest文件如下:

CACHE MANIFEST

# 2012-12-09 v6

jquery-1.11.3.js

index1.html

NETWORK:

*

在html中的引用方式为:

<html  manifest="/Offline/offline.manifest" >


在apache服务器的web.xml中添加mime类型:

<mime-mapping>
<extension>manifest</extension>
<mime-type>text/cache-manifest</mime-type>
</mime-mapping>


controller层次的文件:

如果放在application cache那么所有的文件更新成功才算成功,而且下次才能生效。因为如果我们要升级应用程序那么就会更新这个controller数据,因此更新次数比较多, 建议用localStorage!

View层次的文件:

任何对界面的修改都会更新这个文件,更新频繁,所以用localStorage

Model层次的文件:

建议用localStorage

数据本身:

放入web sql中,即使是上一次的数据也能让用户马上看到

总结:

(1)这种方式和java编程中的MVC还是有一点差异,这种方式只是通过传送一个回调函数一直到Model层,然后在Model层进行相应的回调,也就是Controller层的回调逻辑在Model层才进行了回调。

(2)在整个过程中APP.applicationController扮演了其实就是java编程中的web.xml的作用,也就是服务器启动以后的最开始要做的事情,它可能要构建struts,spring等的运行环境,甚至是通过hibernate初始化底层的数据库,而在这个例子中所有的逻辑全部通过APP.applicationController.start()完成了!

(3)我更愿意用自己的方式来理解MVC,在java中我的理解有Model,Service,Action,View层,其中Model+Service都算是Model层,Action就是control层,View就是jsp等,用来进行数据的显示。在我这个例子中,applicationController是总的controller,是在view层次进行调用的,也就是通过用户的行为来触发这个controller的,如注册hashChange事件,为界面按钮注册click事件,打开本地数据库服务,把用户从服务器获取到的数据保存到本地等一系列总的行为。但是如果特定的事件发生了,要获取数据了,那么他的作用就是调用其它的controller来具体完成业务逻辑,于是就有了AController,AController等一系列完成具体业务逻辑的Controller!

学习indexedDB:

创建数据库:

function runQuery()
{
var request,database,store,index;
request=indexedDB.open("BookDB",2);
request.onerror=function(e)
{
console.log("error");
};
request.onsuccess = function(e) {
};
request.onupgradeneeded=function(event)
{
database=(event.target.result);//获取database对象
store=database.createObjectStore("Books",{keyPath:"id"});
index=store.createIndex("title","title",{unique:false});
console.log(index);
}
}
indexedDB添加数据:

function add(data,callback)
{
var IDBTransaction=window.IDBTransaction||window.webkitIDBTransaction;
var request=indexedDB.open("BookDB",2);
request.onsuccess = function(event){
var database=(request.result);//获取database对象!
/*这是读写事件*/
var store= database.transaction(["Books"],"readwrite").objectStore("Books");
/*添加数据*/
var len=data.length,i=0,addRequest;
while(i<len)
{
addRequest=store.add(data[i]);
/*添加三个数据都成功会打印3次这个信息!*/
addRequest.onsuccess=function(event)
{
console.log("insert three data success!");
};
addRequest.onerror=function(event)
{
console.log("insert three data failure!");
};
i++;
}
/*进行回调,修改界面*/
callback();
};
};
当应用离线的时候从indexedDB中获取数据显示:

/*通过indexedDB选择基本的数据用于在离线的时候显示!也就是离线的时候首先从IndexedDB中选择数据显示*/
function selectBasicData(successCallback)
{
var IDBTransaction=window.IDBTransaction||window.webkitIDBTransaction;
/*获取keyRange来操作,不用游标*/
var IDBKeyRange=window.IDBKeyRange||window.webkitIDBKeyRange;
/*获取表示方向的数据常量*/
var IDBCursor=window.IDBCursor||window.webkitIDBCursor;
/*这里表示小于等于2的键!如果没有true那么就是小于等于3!
var lowerRange=IDBKeyRange.lowerBound(3,true);*/
var request=indexedDB.open("BookDB",2);
request.onsuccess = function(){
var database=(request.result);
/*把IDBKeyRange传递给openCursor方法*/
var store= database.transaction(["Books"],"readwrite").objectStore("Books");
/*通过游标打开存储空间,chrome浏览器不支持lowerRange,因为传入他没有反应!*/
var requestx=store.openCursor(null,IDBCursor.NEXT_NO_DUPLICATE);
/*查询数据*/
var i=0;
var allBooks=[];
requestx.onsuccess=function(event)
{
var cursor=event.target.result;
if(cursor)
{
/*console.log("key="+cursor.key+"value="+JSON.stringify(cursor.value));
不断调用continue方法让游标向下移动!*/
allBooks[i]=cursor.value;
i++;
cursor["continue"]();
}else
{
console.log("already get all!");
}
/*当循环结束以后我们回调,这里的this我们让他指向successCallback!*/
successCallback.call(successCallback,allBooks);
};
requestx.onerror=function(event2)
{
console.log("get data error by cursor rangge!");
}
}
}
note:我在chrome浏览器测试了IDBKeyRange没有支持,因为我把IDBKeyRange传入到openCursor中查询不出结果,同时我们需要用cursor["continue"]()来代替cursor.continue因为continue方法是javascript的关键字!

当用户点击超链接的时候我们通过获取到锚值,然后根据这个锚值去查询indexedDB获取相应的数据显示,这个锚值就是主键,从而应用可以在离线状态下运行:

function get(id,callback)
{
/*指定事务的类型。兼容浏览器*/
var IDBTransaction=window.IDBTransaction||window.webkitIDBTransaction;
var request=indexedDB.open("BookDB",2);
var returnValue;
/*打印数据库名称*/
request.onsuccess = function(event){
var database=(request.result);
/*get方法中传入的是keyPath的键名!*/
var transaction= database.transaction("Books","readwrite");
var interger=parseInt(id);
var request1 = transaction.objectStore("Books").get(interger);
/*成功回调,获取到保存的数据!这里要用event1防止和上面的event重名!
* 因为我们java的id是int,所以这里要用int类型!
* */
request1.onsuccess=function(event1)
{
/*onsuccess里面的result就是数据对象!*/
returnValue=event1.target.result;
/*构建一个数组返回?*/
console.log(returnValue);
/*回调 return returnValue;*/
console.log(callback);
callback.call(null,[returnValue]);
};
};
request.onerror=function(event)
{
console.log("get data error");
};
}
note:我的数据是从服务器动态取过来的,同时他的id是作为indexedDB的主键,该id是int类型,所以要根据锚值查询要把锚值通过parseInt处理!而且get方法是通过主键来获取数据,在他的回调函数onsuccess里面可以通过event.target.result获取返回对象,也可以通过request1.result,因为event.target就是request1!这个结论对于通过index的get方法来获取数据也是成立的!然而对于index的getKey方法虽然也是成立的,但是这时候这时候getKey方法的result是主键,而不是整个对象了!

为了能够让用户根据索引值来查询,我们在界面中用一个select把indexedDB中所有的索引显示在界面上供用户选择:

function showAllIndexForChoose(callbacks)
{
var request=indexedDB.open("BookDB",2);
var database,index,store,requestc;
var returnValue=[];
request.onsuccess = function(event){
database=request.result;
store=database.transaction("Books").objectStore("Books");
index=store.index("title");
/*打开openKeyCursor,返回IDBRequest,如果成功那么就会把IDBCursor对象封装到他的result属性中*/
requestc=index.openKeyCursor();
requestc.onsuccess=function(event1)
{
/*不能通过event1.result.key访问,通过requestc.result.key访问索引,通过requestc.result.primaryKey访问主键*/
var cursor=event1.target.result;
if(cursor)
{
returnValue.push(requestc.result.key);
cursor["continue"]();
}else
{
/*通过游标的回调必须放在else里面,如果放在外面,那么会被调用多次,如放在elese后面,因为每一次游标移动都会调用
* console.log("all keys got!");
console.log("1111111111111111111111");
console.log(returnValue);
callbacks.call(null,returnValue);
console.log("1111333333333111111111");
* !*/
callbacks.call(null,returnValue);
}
};
};
}


note:我们来看看是怎么把所有的索引获取到的,因为这里仅仅是为了获取索引的值,而不需要获取整个对象,所以我们用了index的openKeyCursor,该方法不会返回整个对象。我们可以通过event.target.result来获取cursor用于循环,但是我们要获取真正的数值的时候还是用requestc.result.key和requestc.result.primaryKey访问主键!openCursor和openKeyCursor的主要作用是获取一组数据,而下面的get/getKey只是为了获取到单条数据。而openKeyCursor和openCursor返回的是一个request对象,在onsuccess方法里面通过event1.target.result和request.result可以获取到一个IDBCursor对象,该对象可以获取到primaryKey和key,其中key就是索引值,而primaryKey就是索引值对应的主键值!打开阅读

我们上一步把所有的索引值显示在页面上,当用户点击索引以后我们就需要显示具体的值,代码如下:

function createIndex(indexVal,successCallback)
{
var index,database,store,requestx;
/*打开数据库,同时版本升级!记住第二个参数是字符串,不是数字!*/
var request=indexedDB.open("BookDB","2");
request.onsuccess=function(event)
{
/*获取数据库对象*/
database=(request.result);
/*获取空间*/
store=database.transaction("Books").objectStore("Books");
/*获取索引*/
index=store.index("title");
/*调用索引的get方法返回的是IDBIndex对象,通过这个对象的result获取到返回的值,在chrome浏览器中不能通过event.result.value获取数值!*/
requestx=index.get(indexVal);
/*
*如果要获取主键值,可以通过这种方法index.getKey(indexVal).result
*获取keypath等其它属性通过index.getKey(indexVal).source对象来完成!
*!*/
requestx.onsuccess=function(event1)
{
/*
console.log(requestx.result)
*/
successCallback.call(successCallback,[requestx.result]);
}
}
}
note:通过索引来获取数据是一个常见的需求。我们可以调用索引的get方法来完成,该方法返回IDBIndex对象,通过requestx.result可以获取到通过索引返回的对象!但是,有时候我们不需要获取整个对象,那么我们可以用index.getKey(indexVal)来完成,该对象的result中保存的是主键值,如果要获取到该index的其它属性,如keypath/objectStore/unique信息等,那么就需要利用index.getKey(indexVal).source返回的对象!

note:调用openKeyCursor后通过event.target.result来获取游标对象,成功回调函数放在else里面,那么在遍历所有的数据的时候只会调用一次!这很重要,否则会很影响性能!

有时候我们要获取一个存储空间上的所有的索引:

function getAllIndex()
{
var request=indexedDB.open("BookDB",2);
request.onsuccess=function(event)
{
/*获取数据库*/
var database=request.result,index;
var store=database.transaction("Books").objectStore("Books");
/*获取所有的索引*/
var indexNames=store.indexNames;
var len=indexNames.length,i=0;
while(i<len)
{
index=store.index(indexNames[i]);
/*索引名字通过name属性获取,keyPath是属性名字,unique就是是否唯一!*/
console.log(index.name+index.keyPath+index.unique);
i++;
}
};
}
note:我们通过request.result来获取到数据库对象,然后通过数据库对象来操作存储空间!

获取一个存储空间所有索引可以通过下面的代码来完成:

function getAllIndex()
{
var request=indexedDB.open("BookDB",2);
//返回一个IDBOpenDBRequest对象,当这个请求成功以后,会把数据库对象IDBDatabase封住到该对象的result属性当中!
request.onsuccess=function(event)
{
/*获取数据库*/
var database=request.result,index;
var store=database.transaction("Books").objectStore("Books");
/*获取所有的索引*/
var indexNames=store.indexNames;
var len=indexNames.length,i=0;
while(i<len)
{
index=store.index(indexNames[i]);
/*索引名字通过name属性获取,keyPath是属性名字,unique就是是否唯一!*/
console.log(index.name+index.keyPath+index.unique);
i++;
}
};
}


note:通过我上面提供的图片可以知道,store.indexNames保存的是一个DOMStringList对象,该对象保存的所有的索引的信息,如{0:"title",length:2}这种形式,所以通过store.index传入属性值就能够获取到特定的索引,进而通过IDBIndex对象获取他所有的信息!

清除indexedDB中的所有数据:

function clear()
{
var IDBTransaction=window.IDBTransaction||window.webkitIDBTransaction;
var request=indexedDB.open("BookDB",2);
request.onsuccess = function(event){
var database=(request.result);
/*这是读写事件*/
var store= database.transaction(["Books"],"readwrite").objectStore("Books");
/*打开游标,返回的是一个IDBRequest对象,当openCurosr成功回调以后,他的result属性里面就封装了IDBCursor对象!*/
var cursor=store.openCursor();
cursor.onsuccess=function(event)
{
<span style="white-space:pre">	</span>   var cursor=event.target.result;
if(cursor)
{
/*console.log("key="+cursor.key+"    value="+JSON.stringify(cursor.value));*/
/*请求删除当前项!调用cursor.delete报错missing name after . operator,原因是delete是javascript关键字*/
var deleteRequest=cursor["delete"]();
/*报错:The record may not be deleted inside a read-only transaction.
* 直接把上面的IDBTransaction.READ_WRITE改为readwrite!
* */

var deleteRequest=cursor["delete"]();
/*
* cursor.key就是我们提供的keyPath的值,cursor.value就是我们整个保存的对象!
console.log("after deleting ,key="+cursor.key+"    value="+JSON.stringify(cursor.value));
*/
deleteRequest.onsuccess=function(event)
{
console.log("cursor delete success");
};
deleteRequest.onerror=function(event)
{
console.log("cursor delete error");
};
/*移动到下一个记录!*/
cursor["continue"]();
}else
{
console.log("delete done!");
}
};
/*为游标注册失败回调!*/
cursor.onerror=function(event)
{
console.log("cursor error"+event);
};
};
};


代码中用到了动态创建option添加到select中的方法:

function options(books)
{
/*把返回的索引通过创建option的方法插入到里面*/
var options=$();
for(var i=0,l=books.length;i<l;i++)
{
jQuery.merge(options,$("<option/>").html(books[i]));
}
/*options一开始是一个空的jquery对象,然后每次创建一个对象放在该jquery对象上面
* 最后一次性加入到DOM树中可以减少页面回流的次数!
* */
options.appendTo($("#indexShow"));
}
note:为了减少页面回流导致的性能损失,我们把所有的option逐个添加到一个空的jQuery对象,然后一次性添加到DOM中!

因为setVersion已经失效了,于是我们要在onupgradedneeded中间需要检测一种情况,那就是有多个页面打开了数据库升级前的版本,现在有一个页面要对数据库版本升级,于是我们就用onversionchange和onblocked来对这种情况进行考虑。于是出现下面情况:

function runQuery()
{
var request,database,store,index;
request=indexedDB.open("BookDB",3);
request.onerror=function(e)
{
console.log("error");
};
request.onsuccess = function(e) {
};
request.onupgradeneeded=function(event)
{
database=(event.target.result);
console.log("on version change");
/*当数据库升级的时候,这句代码会被调用,这时候我们关闭数据库,升级以后所有的
访问必须是新的版本号才行,否则以前的访问的代码的的版本号就会低于当前数据库的
版本号,这种情况是不允许出现的!
* */
database.onversionchange=function(e)
{
console.log("are you sure?");
database.close();
};
store=database.createObjectStore("Books",{keyPath:"id"});
index=store.createIndex("title","title",{unique:false});
console.log(index);
};
/*当有旧的页面的时候就要通知用户,当有页面用新的版本号访问的时候我们就通知用户
* 去关闭所有的页面然后完成升级!
* */
request.onblocked=function(event)
{
alert("please close all the tab when changing the database?");
};
}


学习中遇到的异常列表:

(1)Uncaught SyntaxError: Unexpected identifier。因为onsuccess和onerror后面必须有分号!对象后面也要分号,因为我在java中用流读取文件,进而完成离线应用!

(2)Failed to execute 'createObjectStore' on 'IDBDatabase',createObjectStore。必须在onupgradeneeded事件中! IndexedDB 对createObjectStore的使用有明确的限制。因为使用createObjectStore,就是对数据库结构进行修改,所以必须得升级版本号,createObjectStore操作要在onupgradeneeded的事件中执行(即错误提示,所说的需要running a version
change transaction)。调用后然后继续调用onsuccess方法!创建index等对数据库的修改必须在onupgradeneeded里面完成!

(3)setVersion方法已经过时,通过console.log可以知道这个方法已经不存在,代之在open方法中传入版本号.

(4)成功回调获取数据库,必须把其它的代码放在success回调中,因为这里是异步的,在外面访问不到database!

(5)要清楚的认识IndexedDB数据库,请参看这个图查看

(6)我通过Application Cache+localStorage+web sql构建了一个离线应用,同时我也把逻辑按照Application cache+localStorage+indexedDB写了一次,有兴趣的同学可以去我的空间下载。但是这是集成了apache的应用,所以必须在服务器端运行,我已经把所有依赖的jar包都拷贝进去了。但是因为本人不熟悉PHP,因此采用了servlet技术,所以对于几个js和css采用了网络流的读取,服务器端的代码重复性较高,如果有机会我会重新写一次。运行了这个程序你也就大致明白了这几种技术了,同时应用采用了MVC的架构,大家可以好好理解。

(7)HTML5面临的安全问题可以参考这一个博客!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: