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是主键,而不是整个对象了!
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返回的对象!
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面临的安全问题可以参考这一个博客!