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

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面临的安全问题可以参考这一个博客!

你可能感兴趣的:(HTML5本地存储之Web SQL+LocalStorage+ApplicationCache以及indexed+LocalStorage+ApplicationCache构建的离线应用实例代码)