Web离线应用
前言
编写web离线应用,需要把应用文件和数据存在本地,也就是浏览器中。这主要用到了H5的三个重要的知识点,Application Cache(应用程序缓存), localStorage/sessionStorage(web存储), web SQL(web数据库)。
Application cache
如果要启用应用缓存,就需要在html标签加manifest属性1
2
3
4
<html manifest="demo.appcache">
...
</html>
demo.appcache就是写需要缓存页面信息的manifest文件,文件的后缀名可以任写,但建议统一为.appcache。
manifest文件
告知浏览器需要(或不需要)缓存的文件,包括三部分:
- CACHE MANIFEST 此标题下出现的文件将在首次下载后进行缓存
- NETWORK 此标题下列出的文件需要在访问服务器,且不被缓存
- FALLBACK 此标题下列出的文件是页面无法被访问时退回的页面
第一行写CACHE MANIFEST是必须的,写法为1
2
3
4CACHE MANIFEST
index.html
jqury.min.js
#后面的是注释,写上日期和版本号,当index.html等页面有更新时,改日期或者版本号,浏览器会将页面进行更新和缓存,方便。其中能应用默访问的页面,也就是index.html是默认被缓存的,不写也可以。一般写修改不频繁的文件
1 | NETWORK: |
一般写星号,也就是其他的页面都要通过访问服务器
1 | FALLBACK: |
如果无法建立因特网连接,则用 “offline.html” 替代 /html5/ 目录中的所有文件。
更新缓存的情况
- 用户清空浏览器缓存
- manifest文件被修改
- 由程序文件来更新应用缓存
设置MIME-type
需要给manifest文件设置MIME-type为text/cache-manifest
- 在Apache服务器
可以在根目录下添加.htaccess文件或者在manifest文件开头添加(前提是文件是php后缀名)1
AddType text/cache-manifest manifest
==貌似使用nodejs做后台不用设置MIME-type也可以将程序文件写进缓存==1
2
3
header("Content-Type: text/cache-manifest");
localStorage/sessionStorage
在浏览器上(本地)储存用户的浏览数据
两者的区别就在于,localStorage储存的数据没有时间的限制,sessionStorage储存的数据,当关闭浏览器时,数据就没了。
两者的API都是相同的,主要有一下:
- 保存数据:localStorage.setItem(key,value);
- 读取数据:localStorage.getItem(key);
- 删除单个数据:localStorage.removeItem(key);
- 删除所有数据:localStorage.clear();
- 得到某个索引的key:localStorage.key(index);
保存数据还可以直接定义localStorage的属性,比如localStorage.resource = value
一般储存变更频繁的文件
web SQL
位于浏览器中的关系型数据库
核心的方法就三个:
openDatabase:这个方法使用现有数据库或创建新数据库创建数据库对象。
transaction:这个方法允许我们根据情况控制事务提交或回滚,意思是可以划分事务。
executeSql:这个方法用于执行真实的SQL查询。
打开数据库
1 | var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); |
openDatebase方法接受五个参数,分别是:
- 数据名称
- 数据库版本(写死就行)
- 数据库描述
- 大小
- 回调(可选)
插入和读取数据
1 | var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); |
采用MVC思想写js代码
applicationcontroller.js //程序的入口程序主控制器,向外暴露一个start方法,作为程序的入口。其他的私有函数都是对次控制器暴露出的方法的调用
articlescontroller.js //操作articles模型,调用article暴露出的方法实现增删改查以及调用template的方法渲染页面
article.js //article模型,调用更底层的database对象(对数据库操作的封装)
database.js //更底层的M层,封装对数据库的操作
templates.js //V层,插入html节点
程序的入口就只有一个1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//applicationController.js
//webapp的入口函数,类似C语言中的main,或者jq中的$(document).ready
function start(resources, storeResources) {
APP.database.open(function() {
//监听hash的变化
$(window).bind("hashchange", route);
//往DOM里添加CSS
$("head").append('<style>' + resources.css + '</style>');
//创造app应用名称节点
$('body').html(APP.templates.application());
//移除下载提示
$('#loading').remove();
route();
});
if(storeResources) {
localStorage.resources = JSON.stringify(resources);
}
}
在index.html调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60$(document).ready(function() {
console.log('ready %o', new Date()); //%o代替javascript对象
var APP_START_FAILED = "I'm sorry, the app can't start right now."
function startWithResources(resources, storeResources) {
//执行加载的js函数
try {
//eval(resources.js)
insertScript(resources.js);
setTimeout(function() {
APP.applicationController.start(resources, storeResources); //程序入口
}, 500);
} catch(e) {
alert(APP_START_FAILED);
console.log('%o', e);
}
}
function startWithOnlineResources(resources) {
startWithResources(resources, true);
}
function startWithOfflineResources() {
var resources;
//假如之前已经访问了并且js文件已经缓存进localStorage,执行以下
if(localStorage && localStorage.resources) {
resources = JSON.parse(localStorage.resources);
startWithResources(resources, false);
//否则输出提醒信息
}else {
alert(APP_START_FAILED);
}
}
function insertScript(script) {
var node = document.createElement('script');
node.innerHTML = script;
document.head.appendChild(node);
}
//假如设备离线,则执行离线操作
if(navigator && navigator.onLine === false) {
startWithOfflineResources();
//否则,下载资源并执行,假如成功就把资源添加进local storage。
}else {
$.ajax({
url: 'api/resources',
success: startWithOnlineResources,
error: startWithOfflineResources,
dataType: 'json'
});
}
})
程序的设计就类似于树形的结构:
1 | graph TD |
主控制器的第一步工作是打开数据库
对象的封装
全局就一个APP对象,为window的全局变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55window.APP = {};
(function(APP){
APP.applicationController = (function(){
...
return {
start: start
}
}());
APP.articleController = (funcion(){
...
return {
synchronizeWithServer: synchronizeWithServer,
showArticle: showArticle,
showArticleList: showArticleList
}
}());
APP.templates = (function(){}(
...
return {
application: application,
home: home,
articleList: articleList,
article: article,
articleLoading: articleLoading
}
)()};
APP.database = (function(){}(
...
return {
open: open,
runQuery: runQuery
}
)()};
APP.article = (function(){}(
...
return {
deleteArticles: deleteArticles,
insertArticles: insertArticles,
selectBasicArticles: selectBasicArticles,
selectFullArticle: selectFullArticle
}
)()};
}(APP))
这里运用的JS的闭包思想,每一个(function(){…}()()};都是单独的作用域,代码不会被污染,全局就只有一个对象APP.