localStorage实现本地储存树形菜单

本文介绍如何使用localStorage存储树形菜单数据。具体展示了两种存储结构,包括树形菜单的增删改操作及其实现代码。

localStorage实现本地储存树形菜单

最近在写一个Todo-list的页面,页面布局和操作都写完后,想要用localStorage实现本地储存。然而对储存数据的方法一无所知,就先去了解了web的数据储存。

数据储存

常用的web的数据储存有cookie和Web Storage储存机制。

cookie

cookie是“小型文本文件”,主要用途是辨别用户身份、保存用户登录信息。cooki是由服务器端生成、储存在客户端上的数据(通常经过加密)。

  • Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。

  • HTTP请求中的Cookie是明文传递的,安全性成问题。(除非用HTTPS)

  • Cookie的大小限制在4KB左右。于复杂的存储需求来说是不够用的。

Web Storage

Web Storage定义了两种储存数据的对象,localStorage和sessionStorage,它们储存大小一般为5MB,机制如下:

  1. sessionStorage由浏览器存储某个会话(browsing session)的数据,数据在当前会话下有效,刷新页面数据依旧存在,但在关闭页面或浏览器后被清除。

  2. localStorage由浏览器存储数据,数据没有过期时间(expiration time),也就是浏览器关闭再重新打开后数据仍然存在。

localStorage对象的操作

在存储中设置值:Storage.setItem()
从存储中获取值:Storage.getItem()
响应存储的变化:window.addEventListener('storage', function(e){});
删除数据记录:Storage.removeItem() 接受一个参数——你想要移除的数据项的键,然后会将对应的数据项从域名对应的存储对象中移除。Storage.clear() 不接受参数,只是简单地清空域名对应的整个存储对象。

树形菜单的数据储存

在我的todo-list页面中,用户对树的操作有:

  • 在树形菜单上添加、删除任务分类

  • 在任务分类中添加、删除该分类中的任务。
    也是是说用户的每次操作后都要更新存储的数据,以便在刷新页面后显示出用户操作后更改过的页面。

由于本地储存只能存字符串数据,所以存储属性菜单可以用到JSON。用JSON.parse将一个JavaScript对象字符串化为JSON,然后整个存入localStorage来实现保存。用JSON.stringify将JSON字符串解析成为一个JavaScript对象来从存储中获取值。
那么怎么用对象存储树形菜单呢,我想到了两个方式。

方式一

一种是用一个对象treeframe存储树的结构(所有的任务分类,不包括分类中的任务)以及树结点中的任务,存储形式如下。一旦对树形菜单执行任何一项操作,就更新treeframe对象。

var treeframe = {
    "任务列表":{
        "使用说明":[
            {
                "title":"普通任务列表",
                "content":"可以设定任务的名称、任务描述、任务截止时间",
                "endTime":"2016-05-20",
                "type":"normal",
            },
            {
                "title":"添加新分类",
                "content":"通过左侧自定义分类的文件夹图标添加新分类",
                "endTime":"2017-07-20",
                "type":"normal",
            }
        ],
        "今日任务":{},
        "短期任务":{
            "7月":{},
            "8月":{},
            "9月":{}
        },
        "长期任务":{}
     }
}

方式二

另一种方式是用一个对象treeframe存储树的结构,还有一个对象allTasks存储所有结点中的任务。这种方式只需要在添加、删除任务分类时更新树的结构treeframe;添加、删除任务时更改allTasks。因为任务需要添加到树的结构上,所以要记录任务是添加到哪个结点上的,需要为每个树结点添加一个id作为标识以便于在结点上添加任务,树状结构中每个结点的id按照树的先序遍历将结点的id依次储存于数组treeClassify.list中。为了每一个新增的结点的id都不与已存在的id的结点相同,用一个计数器fatherIdjsq来记录可添加的新id。
存储形式如下:

var treeframe = {
    "任务列表":{
        "使用说明":{},
        "今日任务":{},
        "短期任务":{
            "7月":{},
            "8月":{},
            "9月":{}
        },
        "长期任务":{}
     }
}

var allTasks = {
    'list' : [    {
                "fatherId":"fatherId1"
                "title":"普通任务列表",
                "cnode":{"value":"普通任务列表"},
                "pnode":{"value":"使用说明"},
                "content":"可以设定任务的名称、任务描述、任务截止时间",
                "endTime":"2016-05-20",
                "type":"normal",
            },
            {
                "fatherId":"fatherId5"
                "title":"添加新分类",
                "cnode":{"value":"添加新分类"},
                "pnode":{"value":"使用说明"},
                "content":"通过左侧自定义分类的文件夹图标添加新分类",
                "endTime":"2017-07-20",
                "type":"fast",
            }]
};
var treeClassfiy = {"list":["fatherId0","fatherId1","fatherId2","fatherId3","fatherId4","fatherId5","fatherId6","fatherId8","fatherId7"]}
var fatherIdjsq = 9

我觉得如果最开始写整个todo-list时对整个页面的功能和操作以及逻辑都理的特别清楚的话,第一种存储方法一定用起来更加方便。但是我选了第二种,因为这样便于我分别对树的结构和任务列表进行操作,和我之前写好的代码比较容易融合在一起。
所以最终要存储的数据如下:

- 树状结构的存储:对象字面量形式与json的相互转化
- 树状结构中每个结点的id:按照树的先序遍历将结点的id依次储存于数组中
- 所有任务:每一个任务存储于一个对象中,所有任务的对象组成一个数组
- 可以添加的新id

树形菜单的操作与储存

1. 删除任务分类

更改树状结构,在界面上删除这个分类的后,将这个分类的id从存储id的数组中删去。

var jsq = 0;                                                      //计数器jsq用来记录按先序遍历树,并将结点存于数组时,该结点在数组中的位置
preOrder(treedata);                                               //先序遍历treeframe(treeframe初始储存于treedata)
function preOrder(treeframe) {
    for(var key in treeframe) {
        if(jsq===fatherIdArr.indexOf(choseDiv.id)) {              //查找到当前要删除的结点(通过查看当前要删除结点的id在储存结点id的数组(treeClassfiy.list)中的位置)
            delete treeframe[key];                                   //通过detele删除对象的属性来删除结点
            var pos = treeClassifyIdArr.list.indexOf(choseDiv.id);//查找当前要删除的结点id在储存结点id的数组中(treeClassfiy.list)的位置
            treeClassifyIdArr.list.splice(pos,1);                  //把id从数组中删除
            fatherIdArr.splice(jsq, 1);
            break;
        }
        jsq++;
        if(typeof treeframe[key] === "object") {
            preOrder(treeframe[key]);
        }
    }
}
localStorage.setItem('treeframe', JSON.stringify(treedata));      //将更新的treedata存入设置的值treeframe中

2. 添加任务分类

每次手动添加任务分类时,树状结构会改变。也就是说树状结构的改变,意味着树的结点增加/删除了,存储结点id的数组也要改变。
所以要更改树状结构,为新添的分类设置一个新的id,在界面上创建这个分类的后,将这个分类的id插入到存储id的数组中。

var jsq = 0;
preOrder(treedata);
function preOrder(treeframe) {
    for(var key in treeframe) {
        if(jsq===fatherIdArr.indexOf(choseDiv.id)) {             //找到被选中的结点
            fatherIdArr.push("fatherId"+fatherIdjsq);            //先在数组中添加新的id,使新的结点渲染到页面上
            treeframe[key][value] = {};                          //更新树状结构,添加结点(为记录树的结构的对象添加属性)
            preOrder2(treeframe[key]);
            function preOrder2(treeframe) {                      //jsq记录被选中结点的的最后一个子节点
                for(var key in treeframe) {
                    jsq++;
                    if(typeof treeframe[key] === "object") {
                        preOrder2(treeframe[key]);
                    }
                }
            }
            break;
        }
        jsq++;
        if(typeof treeframe[key] === "object") {
            preOrder(treeframe[key]);
        }
    }
}
localStorage.setItem('treeframe', JSON.stringify(treedata));     //将更新的treedata存入设置的值treeframe中
/*此处渲染页面*/ 
fatherIdArr.pop();                                               //新的结点渲染到页面上后删除该结点的id
fatherIdArr.splice(jsq, 0, "fatherId"+fatherIdjsq++);            //更新该结点id在id数组中的位置
treeClassifyIdArr.list = [];                                     //将id数组中的元素添加到对象中
for(var j=0;j<fatherIdArr.length;j++) {
    treeClassifyIdArr.list.push(fatherIdArr[j]);
}
localStorage.setItem('treeClassify', JSON.stringify(treeClassifyIdArr));  //将更新的treeClassifyIdArr存入设置的值treeClassify中
localStorage.setItem('fatherIdjsq', fatherIdjsq);                         //将更新的fatherIdjsq存入设置的值fatherIdjsq中

3. 删除任务

tasklistArr.splice(i,1);
allTasks.list = [];
for(var j=0;j<tasklistArr.length;j++) {
    allTasks.list.push(tasklistArr[j]);
}
localStorage.setItem('allTasks', JSON.stringify(allTasks)); //更新储存

4. 添加任务

allTasks.list = [];
for(var i=0;i<tasklistArr.length;i++) {
    allTasks.list.push(tasklistArr[i]);
}
localStorage.setItem('allTasks', JSON.stringify(allTasks));//更新储存

5. 测试本地存储是否已被填充

if(!localStorage.getItem('treeClassify')) {
//本地没有存储,加载初始设置的页面
} else {
//本地有存储,不加载初始设置的页面,而是根据储存来渲染页面
}

所有存储操作的封装

var Storage = (function(mod) {
    // 删除、添加、编辑快速任务和普通任务的存储
    mod.TaskChange = function() {
        allTasks.list = [];
        for(var i=0;i<tasklistArr.length;i++) {
            allTasks.list.push(tasklistArr[i]);
        }
        localStorage.setItem('allTasks', JSON.stringify(allTasks));
    };
    // 存储记录任务分类(树结点)的id
    mod.treeNodeIdChange = function(jsq) {
        // 将id数组中的元素添加到对象中
        treeClassifyIdArr.list = [];
        for(var j=0;j<d.fatherIdArr.length;j++) {
            treeClassifyIdArr.list.push(d.fatherIdArr[j]);
        }
          // 将更新的treeClassifyIdArr存入设置的值treeClassify中
        localStorage.setItem('treeClassify', JSON.stringify(treeClassifyIdArr));
        // 将更新的d.fatherIdjsq存入设置的值d.fatherIdjsq中
        localStorage.setItem('d.fatherIdjsq', d.fatherIdjsq);
    };

    // 添加任务分类后的存储
    mod.addClassifySto = function(choseDiv,value) {
        console.log("addClassifySto");
        var jsq = 0;
        preOrderAdd(d.treedata);
        function preOrderAdd(treeframe) {
            for(var key in treeframe) {
                      // 找到被选中的结点
                      if (jsq===d.fatherIdArr.indexOf(choseDiv.id)) {
                      // 先在数组中添加新的id,使新的结点渲染到页面上
                    d.fatherIdArr.push("fatherId"+d.fatherIdjsq); 
                    // 更新树状结构,添加结点(为记录树的结构的对象添加属性)
                    treeframe[key][value] = {};
                    preOrder2(treeframe[key]);
                    break;
                }
                jsq++;
                if (typeof treeframe[key] === "object") {
                    preOrderAdd(treeframe[key]);
                }
            }
        }
        // jsq记录被选中结点的的最后一个子节点
        function preOrder2(treeframe) {
            for(var key in treeframe) {
                jsq++;
                if (typeof treeframe[key] === "object") {
                    preOrder2(treeframe[key]);
                }
            }
        }
        // 将更新的treedata存入设置的值treeframe中
        localStorage.setItem('treeframe', JSON.stringify(d.treedata));
        return jsq;
    };
    // 删除任务分类后的存储
    mod.delClassifySto = function(choseDiv) {
        // tree
        console.log("delClassifySto");
        var jsq = 0;
        preOrderDel(d.treedata);
        function preOrderDel(treeframe) {
            for(var key in treeframe) {
                if (jsq===d.fatherIdArr.indexOf(choseDiv.id)) {
                    delete treeframe[key];
                    var pos = treeClassifyIdArr.list.indexOf(choseDiv.id);
                    treeClassifyIdArr.list.splice(pos,1);
                    d.fatherIdArr.splice(jsq, 1);
                    break;
                }
                jsq++;
                if (typeof treeframe[key] === "object") {
                    preOrderDel(treeframe[key]);
                }
            }
        }
        localStorage.setItem('treeframe', JSON.stringify(d.treedata));
        return jsq;
    };
    return mod;
})(Storage||{});

需要注意的地方

加载初始设置的页面的过程中,不需要存储任何数据,也就是不需要操作localStorage对象。这样的话如果用户不对页面进行操作,就没有任何数据需要存储。只有在用户对页面进行操作后,才会储存并再页面再次刷新时按照储存的内容进行页面的渲染。

在线查看demo

在线demo可供参考,欢迎review代码,求批评、建议和交流:p
localStorage实现本地储存树形菜单_demo

参考资料

详说 Cookie, LocalStorage 与 SessionStorage
JSON.stringify
链使用 Web Storage API

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值