TWaver HTML5 + Node.js + express + socket.io + redis(五)
Posted on 2012-03-05 17:09 TWaver 閱讀(1927) 評(píng)論(0) 編輯 收藏接上一回TWaver HTML5 + Node.js + express + socket.io + redis(四), 這一篇您將了解到
1. 如何保存更改后的拓?fù)鋽?shù)據(jù) (包括新增的, 修改的, 刪除的)
2. 如何廣播更改后的拓?fù)鋽?shù)據(jù) (僅僅廣播更改的數(shù)據(jù))
下面是mac和iphone上的效果圖, mac或iphone上的修改都將及時(shí)互相同步:
一. 先來看后臺(tái)如何實(shí)現(xiàn)
后臺(tái)需要做兩件事情: 保存修改以及廣播修改; 其中修改又分為是新增, 修改, 還是刪除. 保存修改很容易, 無非就是對(duì)數(shù)據(jù)庫的增刪改, 廣播數(shù)據(jù)也很容易, 調(diào)用Socket.emit之前, 先設(shè)置廣播標(biāo)記就ok了: Socket.broadcast.emit. 而且這個(gè)廣播只會(huì)通知其他客戶端, 發(fā)送這個(gè)廣播的客戶端不會(huì)收到廣播, 這正好是我們需要的, 所以后臺(tái)代碼就好寫了:
添加保存數(shù)據(jù)的socket.io監(jiān)聽, 里面保存數(shù)據(jù)后, 廣播之:
1 //保存數(shù)據(jù)
2 client.on('saveData', function (datas) {
3 if (!datas) {
4 return;
5 }
6 //保存新增網(wǎng)元
7 save(datas.add);
8 //保存修改網(wǎng)元
9 save(datas.change);
10 //刪除網(wǎng)元
11 remove(datas.remove);
12 //廣播更新
13 client.broadcast.emit('broadcast', datas);
14 });
2 client.on('saveData', function (datas) {
3 if (!datas) {
4 return;
5 }
6 //保存新增網(wǎng)元
7 save(datas.add);
8 //保存修改網(wǎng)元
9 save(datas.change);
10 //刪除網(wǎng)元
11 remove(datas.remove);
12 //廣播更新
13 client.broadcast.emit('broadcast', datas);
14 });
保存數(shù)據(jù)的函數(shù)如下:
1 //保存網(wǎng)元
2 function save (datas) {
3 if(!datas){
4 return;
5 }
6 var elements = {};
7 for (var i=0,n=datas.length,data; i<n; i++) {
8 data = datas[i];
9 elements[data.id] = JSON.stringify(data);
10 }
11 redis.hmset('datas', elements);
12 };
13
14 //刪除網(wǎng)元
15 function remove (datas) {
16 if(!datas){
17 return;
18 }
19 var ids = [];
20 for (var i=0,n=datas.length; i<n; i++) {
21 ids.push(datas[i].id);
22 }
23 redis.hdel('datas', ids);
24 };
2 function save (datas) {
3 if(!datas){
4 return;
5 }
6 var elements = {};
7 for (var i=0,n=datas.length,data; i<n; i++) {
8 data = datas[i];
9 elements[data.id] = JSON.stringify(data);
10 }
11 redis.hmset('datas', elements);
12 };
13
14 //刪除網(wǎng)元
15 function remove (datas) {
16 if(!datas){
17 return;
18 }
19 var ids = [];
20 for (var i=0,n=datas.length; i<n; i++) {
21 ids.push(datas[i].id);
22 }
23 redis.hdel('datas', ids);
24 };
二. 前臺(tái)實(shí)現(xiàn)
分兩步: 監(jiān)聽數(shù)據(jù)的增刪改并自動(dòng)保存, 響應(yīng)廣播更新
1. 監(jiān)聽數(shù)據(jù)的增刪改并自動(dòng)保存
TWaver HTML5的數(shù)據(jù)模型提供了各種監(jiān)聽器, 以便在數(shù)據(jù)更改后做響應(yīng)處理. 其中:
DataBox.addDataPropertyChangeListener用于監(jiān)聽數(shù)據(jù)容器里數(shù)據(jù)的屬性變化, 也即對(duì)數(shù)據(jù)的修改, 其回調(diào)函數(shù)的參數(shù)包含屬性: property(發(fā)生變化的屬性), oldValue(舊值), newValue(新值), source(發(fā)生變化的數(shù)據(jù))
DataBox.addDataBoxChangeListener用于監(jiān)聽數(shù)據(jù)容器的變化, 也即數(shù)據(jù)的添加, 刪除以及清空, 其回調(diào)函數(shù)的參數(shù)包含屬性: kind(容器變化類型, 可選值為add(新增), remove(刪除), clear(清空)), data(新增或刪除的數(shù)據(jù)), datas(清空的數(shù)據(jù)集合)
這里實(shí)現(xiàn)保存數(shù)據(jù)的思路是:
i> 如果有網(wǎng)元被修改, 則將被修改的網(wǎng)元加上change標(biāo)記, 其中判斷isChanging標(biāo)志很重要, 因?yàn)樾枰么藰?biāo)志區(qū)分是用戶更改還是響應(yīng)廣播更新(也即程序更新)
1 //添加網(wǎng)元屬性更改監(jiān)聽器
2 box.addDataPropertyChangeListener(function (e) {
3 //如果正在響應(yīng)廣播更新,則返回
4 if (isChanging) {
5 return;
6 }
7 //如果屬性為add, change 則返回
8 if (e.property === 'C:add' || e.property === 'C:change') {
9 return;
10 }
11 //設(shè)置保存數(shù)據(jù)標(biāo)記
12 needSave = true;
13 //將有屬性更改的節(jié)點(diǎn)置上change標(biāo)記
14 e.source.setClient('change', true);
15 });
2 box.addDataPropertyChangeListener(function (e) {
3 //如果正在響應(yīng)廣播更新,則返回
4 if (isChanging) {
5 return;
6 }
7 //如果屬性為add, change 則返回
8 if (e.property === 'C:add' || e.property === 'C:change') {
9 return;
10 }
11 //設(shè)置保存數(shù)據(jù)標(biāo)記
12 needSave = true;
13 //將有屬性更改的節(jié)點(diǎn)置上change標(biāo)記
14 e.source.setClient('change', true);
15 });
ii> 如果有新增網(wǎng)元, 則將新增網(wǎng)元加上add標(biāo)記, 如果有網(wǎng)元被刪除, 則存入map中
1 //添加數(shù)據(jù)容器更改監(jiān)聽器
2 box.addDataBoxChangeListener( function (e) {
3 //如果正在響應(yīng)廣播更新,則返回
4 if (isChanging) {
5 return;
6 }
7 //設(shè)置保存數(shù)據(jù)標(biāo)記
8 needSave = true;
9 if (e.kind === 'add') {
10 //將新增的節(jié)點(diǎn)置上add標(biāo)記
11 e.data.setClient('add', true);
12 } else if (e.kind === 'remove') {
13 //如果網(wǎng)元沒有新增標(biāo)記, 則存儲(chǔ)到被刪除網(wǎng)元列表中
14 if (!e.data.getClient('add')) {
15 //存儲(chǔ)被刪除網(wǎng)元
16 removedElements[e.data.getId()] = e.data;
17 }
18 }
19 });
2 box.addDataBoxChangeListener( function (e) {
3 //如果正在響應(yīng)廣播更新,則返回
4 if (isChanging) {
5 return;
6 }
7 //設(shè)置保存數(shù)據(jù)標(biāo)記
8 needSave = true;
9 if (e.kind === 'add') {
10 //將新增的節(jié)點(diǎn)置上add標(biāo)記
11 e.data.setClient('add', true);
12 } else if (e.kind === 'remove') {
13 //如果網(wǎng)元沒有新增標(biāo)記, 則存儲(chǔ)到被刪除網(wǎng)元列表中
14 if (!e.data.getClient('add')) {
15 //存儲(chǔ)被刪除網(wǎng)元
16 removedElements[e.data.getId()] = e.data;
17 }
18 }
19 });
iii> 啟動(dòng)定時(shí)器, 保存更改
定時(shí)器代碼如下:
1 //自動(dòng)保存
2 function setAutoSave (value) {
3 if(value){
4 //定時(shí)器,每隔1秒鐘保存修改
5 timer = setInterval(function(){
6 save();
7 }, 1000);
8 }else{
9 window.clearTimeout(timer);
10 }
11 }
2 function setAutoSave (value) {
3 if(value){
4 //定時(shí)器,每隔1秒鐘保存修改
5 timer = setInterval(function(){
6 save();
7 }, 1000);
8 }else{
9 window.clearTimeout(timer);
10 }
11 }
保存數(shù)據(jù)代碼如下, 需要注意的是, 得到要添加和被修改的網(wǎng)元后, 需要將其add和change標(biāo)記置為false, 以避免重復(fù)保存:
1 //保存數(shù)據(jù)
2 function save () {
3 //如果無數(shù)據(jù)添加、修改、刪除,則直接返回
4 if(!needSave){
5 return;
6 }
7
8 var add = [];
9 var change = [];
10 var remove = [];
11 isChanging = true;
12 box.forEach(function(data){
13 //新增的網(wǎng)元
14 if(data.getClient('add')){
15 add.push(toData(data));
16 }
17 //修改的網(wǎng)元
18 else if(data.getClient('change')){
19 change.push(toData(data));
20 }
21 //清除網(wǎng)元新增和修改標(biāo)記
22 data.setClient('add', false);
23 data.setClient('change', false);
24 });
25 isChanging = false;
26 //刪除的網(wǎng)元
27 for(var id in removedElements){
28 remove.push(toData(removedElements[id]));
29 }
30
31 if(add.length == 0 && change.length == 0 && remove.length == 0){
32 return;
33 }
34
35 //初始化待刪除數(shù)據(jù)
36 removedElements = {};
37 //還原保存數(shù)據(jù)標(biāo)記
38 needSave = false;
39 //構(gòu)造更新數(shù)據(jù)
40 var datas = {};
41 if(add.length != 0){
42 datas.add = add;
43 }
44 if(remove.length != 0){
45 datas.remove = remove;
46 }
47 if(change.length != 0){
48 datas.change = change;
49 }
50 socket.emit('saveData', datas);
51 }
2 function save () {
3 //如果無數(shù)據(jù)添加、修改、刪除,則直接返回
4 if(!needSave){
5 return;
6 }
7
8 var add = [];
9 var change = [];
10 var remove = [];
11 isChanging = true;
12 box.forEach(function(data){
13 //新增的網(wǎng)元
14 if(data.getClient('add')){
15 add.push(toData(data));
16 }
17 //修改的網(wǎng)元
18 else if(data.getClient('change')){
19 change.push(toData(data));
20 }
21 //清除網(wǎng)元新增和修改標(biāo)記
22 data.setClient('add', false);
23 data.setClient('change', false);
24 });
25 isChanging = false;
26 //刪除的網(wǎng)元
27 for(var id in removedElements){
28 remove.push(toData(removedElements[id]));
29 }
30
31 if(add.length == 0 && change.length == 0 && remove.length == 0){
32 return;
33 }
34
35 //初始化待刪除數(shù)據(jù)
36 removedElements = {};
37 //還原保存數(shù)據(jù)標(biāo)記
38 needSave = false;
39 //構(gòu)造更新數(shù)據(jù)
40 var datas = {};
41 if(add.length != 0){
42 datas.add = add;
43 }
44 if(remove.length != 0){
45 datas.remove = remove;
46 }
47 if(change.length != 0){
48 datas.change = change;
49 }
50 socket.emit('saveData', datas);
51 }
從網(wǎng)元獲取要持久化的數(shù)據(jù)代碼如下:
1 //從網(wǎng)元獲取持久化數(shù)據(jù)
2 function toData (element) {
3 if(element instanceof twaver.Node) {
4 return {
5 id: element.getId(),
6 name: element.getName(),
7 location: element.getLocation()
8 };
9 } else {
10 return {
11 id: element.getId(),
12 name: element.getName(),
13 from: element.getFromNode() ? element.getFromNode().getId() : null,
14 to: element.getToNode() ? element.getToNode().getId() : null
15 };
16 }
17 }
2 function toData (element) {
3 if(element instanceof twaver.Node) {
4 return {
5 id: element.getId(),
6 name: element.getName(),
7 location: element.getLocation()
8 };
9 } else {
10 return {
11 id: element.getId(),
12 name: element.getName(),
13 from: element.getFromNode() ? element.getFromNode().getId() : null,
14 to: element.getToNode() ? element.getToNode().getId() : null
15 };
16 }
17 }
2. 響應(yīng)廣播更新
分為新增, 刪除以及修改三種情況, 其中很重要的是在做這些事情之前先設(shè)置isChanging為true, 以防止重復(fù)廣播更新
1 //響應(yīng)廣播更新
2 function onBroadcast (data) {
3 if (!data) {
4 return;
5 }
6 //設(shè)置響應(yīng)廣播更新標(biāo)記,防止重復(fù)更新
7 isChanging = true;
8 var i, c, add=data.add, change=data.change, remove=data.remove;
9 //添加節(jié)點(diǎn)
10 if (add) {
11 for (i=0,c=add.length; i<c; i++) {
12 if (add[i].from) {
13 addLink(add[i]);
14 } else {
15 addNode(add[i]);
16 }
17 }
18 }
19 //修改節(jié)點(diǎn)
20 if (change) {
21 for (i=0,c=change.length; i<c; i++) {
22 changeData(change[i]);
23 }
24 }
25 //刪除節(jié)點(diǎn)
26 if (remove) {
27 for (i=0,c=remove.length; i<c; i++) {
28 removeData(remove[i]);
29 }
30 }
31 //還原更新廣播數(shù)據(jù)
32 isChanging = false;
33 }
2 function onBroadcast (data) {
3 if (!data) {
4 return;
5 }
6 //設(shè)置響應(yīng)廣播更新標(biāo)記,防止重復(fù)更新
7 isChanging = true;
8 var i, c, add=data.add, change=data.change, remove=data.remove;
9 //添加節(jié)點(diǎn)
10 if (add) {
11 for (i=0,c=add.length; i<c; i++) {
12 if (add[i].from) {
13 addLink(add[i]);
14 } else {
15 addNode(add[i]);
16 }
17 }
18 }
19 //修改節(jié)點(diǎn)
20 if (change) {
21 for (i=0,c=change.length; i<c; i++) {
22 changeData(change[i]);
23 }
24 }
25 //刪除節(jié)點(diǎn)
26 if (remove) {
27 for (i=0,c=remove.length; i<c; i++) {
28 removeData(remove[i]);
29 }
30 }
31 //還原更新廣播數(shù)據(jù)
32 isChanging = false;
33 }
增刪改網(wǎng)元代碼如下:
1 //添加節(jié)點(diǎn)
2 function addNode (data) {
3 //構(gòu)造節(jié)點(diǎn)
4 var node = new twaver.Node(data);
5 //添加節(jié)點(diǎn)
6 box.add(node);
7 }
8
9 //添加節(jié)點(diǎn)
10 function addLink (data) {
11 //查找from節(jié)點(diǎn)
12 var from = box.getDataById(data.from);
13 //查找to節(jié)點(diǎn)
14 var to = box.getDataById(data.to);
15 //構(gòu)造連線
16 var link = new twaver.Link({id: data.id, name: data.name}, from, to);
17 //添加連線
18 box.add(link);
19 }
20
21 //修改網(wǎng)元
22 function changeData (data) {
23 var element = box.getDataById(data.id);
24 //修改節(jié)點(diǎn)
25 if (element instanceof twaver.Node) {
26 element.setLocation(data.location.x, data.location.y);
27 element.setName(data.name);
28 }
29 //修改連線
30 else if (element instanceof twaver.Link) {
31 element.setName(data.name);
32 element.setFromNode(box.getDataById(data.from));
33 element.setToNode(box.getDataById(data.to));
34 }
35 //網(wǎng)元被刪除
36 else {
37 //網(wǎng)元被刪除, 無需修改
38 }
39 }
40
41 //刪除網(wǎng)元
42 function removeData (data) {
43 box.removeById(data.id);
44 }
2 function addNode (data) {
3 //構(gòu)造節(jié)點(diǎn)
4 var node = new twaver.Node(data);
5 //添加節(jié)點(diǎn)
6 box.add(node);
7 }
8
9 //添加節(jié)點(diǎn)
10 function addLink (data) {
11 //查找from節(jié)點(diǎn)
12 var from = box.getDataById(data.from);
13 //查找to節(jié)點(diǎn)
14 var to = box.getDataById(data.to);
15 //構(gòu)造連線
16 var link = new twaver.Link({id: data.id, name: data.name}, from, to);
17 //添加連線
18 box.add(link);
19 }
20
21 //修改網(wǎng)元
22 function changeData (data) {
23 var element = box.getDataById(data.id);
24 //修改節(jié)點(diǎn)
25 if (element instanceof twaver.Node) {
26 element.setLocation(data.location.x, data.location.y);
27 element.setName(data.name);
28 }
29 //修改連線
30 else if (element instanceof twaver.Link) {
31 element.setName(data.name);
32 element.setFromNode(box.getDataById(data.from));
33 element.setToNode(box.getDataById(data.to));
34 }
35 //網(wǎng)元被刪除
36 else {
37 //網(wǎng)元被刪除, 無需修改
38 }
39 }
40
41 //刪除網(wǎng)元
42 function removeData (data) {
43 box.removeById(data.id);
44 }
最后, 附上本文的完整demo:TWaverHTML5Demo