你在浏览网站时一定遇到过这种情况:当你兴致勃勃的在某个网站的留言板或者评论区洋洋洒洒的写了N长的自己的看法或观点,却总会由于各种原因(如突然断网、浏览器假死、系统崩溃等)而导致未发表前就丢失了这些好不容易才写下的内容。当你重新打开浏览器时,这些东西再也无法恢复了,浪费了半天感情。
使用wordpress的同学都知道wp的评论功能处的资料输入框是可以记录你上次评论时输入的姓名、邮箱、网站地址的。这是通过cookie记录的。你可能也想过通过cookie记录评论框输入的内容,但事实是cookie记录信息有限,太长的评论内容可能无法容身,而且由于cookie在每次http请求时都会被提交到服务器,为了尽可能的减少通信延迟,我们就要确保浏览器中存储尽量少的cookie。
客户端存储方案
而客户端存储除了cookie,其实还有好多其他方法,如Google Gears、flash、User Data、window.name等,但是这些方法或多或少都由于实现麻烦或者跨浏览器问题而不能单独使用。
综述各类方法,较为方便的一个实现方案是,对于较为现代的浏览器(IE8、IE9、chrome、Firefox、opera等),由于都已经实现了支持localStorage存储方案,所以可以使用这一W3C提倡的方案;而早期的IE则可以使用user Data方案。
wordpress上的评论内容存储实现思路
OK,有了基本的客户端存储方案,那么对于“wordpress永不丢失的评论”这个目标该怎么实现呢?
首先就是要考虑存储评论内容,由于每篇文章、每个页面都不同,所以为了区分每个存储,我们可以使用当前页面或者文章的ID来作为存储键值的参考。这个值可以通过获取页面中“input#comment_post_ID”这个隐藏域的值取得。然后我们可以通过绑定评论框的“onchange”事件来触发设置本地存储。
如果输入的内容没有被发表成功,那么在浏览器刷新或者重新加载此页面时,就要试着去读取相关内容,并显示到评论框里。
在评论发表成功后就要清除本页面上的这个存储或者将其置为空值。
具体代码及分析注释
先看存储功能的实现代码:
/*!
* @public
* @update 2010/12/11, liuqiqi imqiqiboy#gmail.com http://www.qiqiboy.com
* @param <String> key 键值
* @param <String> value 要存储的值
* @call 设置新值storeLocalData(key, value); || 清除某个存储storeLocalData(key, ''); ||
* 获取某个存储storeLocalData(key); || 清除所有存储storeLocalData();
*/
var storeLocalData=function(key, value) {
try {
//先试着获取对localStorage的引用
var storage=window.localStorage || window.globalStorage && window.globalStorage[location.hostname];
if (storage){//如果获取localStorage对象成功,说明浏览器支持localStorage
if (value !== undefined)//如果存在value,表示是要设置存储操作
value == "" ? storage.removeItem(key) : storage[key] = value;
else if (key !== undefined)//存在key值,就返回相应的存储值
return storage[key] || "";
else throw new Error();//抛出异常,使代码流跳到最后清除存储部分
}else if (/MSIE/i.test(navigator.userAgent)) {//确保是IE,IE才能使用userData存储
var userData = document.getElementById('userData') || document.createElement('input');//试着查找‘userData’这个节点,没有就开始创建
if (!userData.id) {//没有ID值就说明页面上无此节点
userData.id="userData";
userData.style.display='none';
userData.style.behavior='url(#default#userData)';//behavior:url(#default#userData)'表示开启user Data存储
document.body.appendChild(userData);//加载到页面中
}
try {
userData.load("oXMLBranch")//尝试载入存储的数据
} catch(e) {}
if (value !== undefined) {//这里同上面,其实也是通过value判断是设置新值还是返回存储的值
value == "" ? userData.removeAttribute(key) : userData.setAttribute(key, value);
userData.save("oXMLBranch");//保存
}else if (key !== undefined)//存在key值,就返回相应的存储值
return userData.getAttribute(key) || "";
else throw new Error();//抛出异常,使代码流跳到最后清除存储部分
} else return false;//操作失败
return true;//设置成功
} catch(e) {//捕获到上面抛出的错误
if(storage){
storage.clear();//使用W3C的方法清除存储
} else if (userData) {
var attrs = userData.xmlDocument.firstChild.attributes, i;
while(i = attrs.length){//循环清除xml文件里的每个属性里保存的值
var j = attrs[--i].nodeName;
userData.removeAttribute(j);
}
userData.save("oXMLBranch");
}
}
}
//以下为对localStorage测试的补充,部署到wordpress时不必添加此段代码
//设置一个新的存储
storeLocalData('new_key', 'new_value');//即保存了一个名称为new_key,内容为new_value的存储
//获取某个存储
storeLocalData('new_key');//即会返回new_key对应的值,如果new_key上存储了内容,就返回存储的内容,否则返回空字串
//删除某个存储
storeLocalData('new_key', '');//即将某个存储设置为空,即会删除它
//删除页面上所有的存储
storeLocalData();//传递任何参数,即会执行清除操作;你可以任何时候通过在浏览器的JS控制台上输入此函数来执行清理。
//查看输出页面上所有的storage
var storage = window.localStorage;
if(!storage.length)document.write('本页面上无存储内容!');
for(var i=0; i<storage.length; i++){
//输出格式为 "键值 = 存储值"
document.write(storage.key(i)+' = '+storage.getItem(storage.key(i))+'<br/>');
}
根据我代码中的注释,其实可以看出,设置一个新的存储,就调用storeLocalData(键值, 新值);获取一个存储,就只提供键值即可,storeLocalData(键值);
然后要和wordpress的评论机制结合起来。
//这些代码务必放在评论框下方,否则就使用window.onload包含调用,或者jQuery的ready方法调用
function $(id){
return document.getElementById(id);
}
function addListener(e, n, o, u) {
if(e.addEventListener) {
e.addEventListener(n, o, u);
return true;
} else if(e.attachEvent) {
e['e' + n + o] = o;
e[n + o] = function() {
e['e' + n + o](window.event);
};
e.attachEvent('on' + n, e[n + o]);
return true;
}
return false;
}
/*通过onchange绑定存储行为方式,已弃用该方法,新方法在下边
if($("comment")){
//这里用作存储键值我使用了 'storage_'+数字ID 的方式,因为我测试好像数字字串不能作为localStorage的属性值,原因我暂时不明,有了解的可以提点一下
$('comment').value=storeLocalData('storage_'+$('comment_post_ID').value);
addListener($('comment'),'change',function(){//绑定 onchange 事件
storeLocalData('storage_'+$('comment_post_ID').value, $('comment').value);
},false);
addListener($('commentform'),'submit',function(){//绑定评论表单的 onsubmit 事件
storeLocalData('storage_'+$('comment_post_ID').value, '');//这里置为空就可以了
},false);
}
*/
//以下内容为2010.12.14日更新
if($("comment")){
//这里用作存储键值我使用了 'storage_'+数字ID 的方式,因为我测试好像数字字串不能作为localStorage的属性值,原因我暂时不明,有了解的可以提点一下
var local=storeLocalData('storage_'+$('comment_post_ID').value), post_id=$('comment_post_ID').value, //获取之前存储的内容和文章id
storeTimer, new_value, old_value=local;//声明定时器标志,新值、旧值的变量
$('comment').value= local === undefined? '' : local;//将评论框内容更新为存储的值
addListener($('comment'),'focus',function(){//绑定聚焦事件
if(!storeTimer)
storeTimer=setInterval(function(){//声明一个定时器,每秒执行一次
//if语句的执行会先获取评论框中的值,然后和旧值比较,不同就存储新值,然后就新值赋给旧值,以便下次继续和再次取得新值比较
if((new_value=$('comment').value)!==undefined && new_value!=old_value && (old_value=new_value)!==undefined)
storeLocalData('storage_'+post_id, new_value);
},1000);
},false);
addListener($('comment'),'blur',function(){
clearInterval(storeTimer);storeTimer=undefined;//焦点移开就清楚定时器
},false);
addListener($('commentform'),'submit',function(){//绑定评论表单的 onsubmit 事件
storeLocalData('storage_'+$('comment_post_ID').value, '');//这里置为空就可以了
},false);
}
清除存储我是通过submit事件来执行的,其实更好的方案是修改你的ajax评论代码,在评论发表成功后的回调函数中调用storeLocalData(‘storage_’+$(‘comment_post_ID’).value, ‘’);
对于存储评论内容,我暂时使用onchange,其实我觉得这样并不是最好的,因为onchange事件必须你修改过内容后而且使评论框失去焦点才会触发,但很有可能我们正在书写时浏览器就已经崩溃,此时就无法保存内容了。所以比较保险写的方法是绑定聚焦事件(onfocus),然后通过定时器每过一段时间就执行设置存储。
2010.12.14更新,已经改为onfocus事件与定时器结合方式。具体请详看上方代码。
具体的实例
关于本地化客户端存储的例子,像腾讯微博、新浪微博都有应用,你在微博发表框中输入一些内容,然后不要发表;接着关闭浏览器重新打开或者重载页面,就会发现已经输入的内容还会出现在输入框中。
本文所讲的wordpress的评论框存储,可以参看本博的输入框(仅限SimPaled主题,如果你在使用其他主题,请通过右下角的主题切换功能切换到SimPaled)。你可以在任意页面的评论部分输入一些内容,然后点击一下页面其他部分(目的是让评论框失去焦点,触发onchange事件)。你此时可以关闭浏览器或者刷新页面,输入的内容会自动补充进评论框里(请注意如果你以前访问过博客,先强刷浏览器清除JS文件的缓存)。
由于条件限制,我并没有测试过所有浏览器,所以请大家帮忙测试(主要是IE6、IE7),如有问题,欢迎即时告知。
注:已知IE7\IE6下貌似有问题,存储失败。请各位提供更多关于此的测试结果,谢谢。
