首先,上我的网站的留言页面,你们能够看看效果:留言板javascript
这里也上个效果图php
前端为了省事,使用jQuery编写,后台使用php简单读写mysql数据库。css
数据库建立了一个表:comments,结构以下图:html
所有评论(包括文章评论回复,留言板)都写在同一张表中,不一样的评论区用字段belong区分前端
同一个评论区里,parent为0表示为评论,parent为某值时表示为哪一个评论的回复,思路不复杂。java
注意,这里并不讲CSS,你们根据本身的须要定制,如今开始封装:node
咱们根据本身的须要定下功能,首先个人网站并无实现消息提醒,即时通信的功能,因此评论回复并不会提示站长或者用户,只会对留言区产生效果,因此咱们只要简单实现如下功能:mysql
显示评论列表ajax
可以提交评论sql
进行回复
咱们将评论的功能封装成一个类,经过实例化就能建立不一样的评论区,因此不难想到,
实例化的时候咱们须要传入的参数可能有:评论区的id、获取评论的php地址,提交评论的php地址。
因此咱们能够猜测实例化评论区的代码可能为:
var oCmt = new Comment({
parent: $('#box'), //你想要将这个评论放到页面哪一个元素中
id: 0,
getCmtUrl: './php/getcomment.php',
setCmtUrl: './php/comment.php'
})
固然,我是在Comment类上定义一个静态方法
Comment.allocate({ parent: $('#box'), id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
大同小异,只是初始化的地方不一样而已
function Comment(options){
this.belong = options.id;
this.getCmtUrl = options.getCmtUrl;
this.setCmtUrl = options.setCmtUrl;
this.lists = [];
this.keys = {};
this.offset = 5;
}
var fn = Comment.prototype;
Comment.allocate = function(options){
var oCmt = new Comment(options);
if (oCmt.belong == undefined || !oCmt.getCmtUrl || !oCmt.setCmtUrl) {
return null;
};
oCmt.init(options);
return oCmt;
};
里面的变量和方法咱们慢慢解释,若是你不定义一个allocate方法,那么能够写成:
function Comment(options){
this.belong = options.id;
this.getCmtUrl = options.getCmtUrl;
this.setCmtUrl = options.setCmtUrl;
this.lists = [];
this.keys = {};
this.offset = 5;
if (this.belong == undefined || !this.getCmtUrl || !this.setCmtUrl) {
return null;
};
this.init(options)
}
var fn = Comment.prototype;
变量先不说,像我都是先写功能函数,而后须要添加属性变量再回头来添加,咱们只须要看到构造函数最后执行了:
this.init(options)
从名字能够看出是初始化函数。
fn.init = function (options) {
//初始化node
this.initNode(options);
//将内容放进容器
this.parent.html(this.body);
//初始化事件
this.initEvent();
//获取列表
this.getList();
};
fn为Comment.prototype,只说一次,下面就再也不说了。
初始化就是有4个工做要作,从代码注释能够看出,如今一个一个讲解
从名字能够看出主要初始化节点或者缓存dom
fn.initNode = function(options){
//init wrapper box
if (!!options.parent) {
this.parent = options.parent[0].nodeType == 1 ? options.parent : $('#' + options.parent);
};
if (!this.parent) {
this.parent = $('div');
$('body').append(this.parent);
}
//init content
this.body = (function(){
var strHTML = '<div class="m-comment">' +
'<div class="cmt-form">' +
'<textarea class="cmt-text" placeholder="欢迎建议,提问题,共同窗习!"></textarea>' +
'<button class="u-button u-login-btn">提交评论</button>' +
'</div>' +
'<div class="cmt-content">' +
'<div class="u-loading1"></div>' +
'<div class="no-cmt">暂时没有评论</div>' +
'<ul class="cmt-list"></ul>' +
'<div class="f-clear">' +
'<div class="pager-box"></div>' +
'</div>' +
'</div>' +
'</div>';
return $(strHTML);
})();
//init other node
this.text = this.body.find('.cmt-text').eq(0);
this.cmtBtn = this.body.find('.u-button').eq(0);
this.noCmt = this.body.find('.no-cmt').eq(0);
this.cmtList = this.body.find('.cmt-list').eq(0);
this.loading = this.body.find('.u-loading1').eq(0);
this.pagerBox = this.body.find('.pager-box').eq(0);
};
代码中咱们能够看出:
this.parent : 保存的是容器节点
this.body : 保存的是评论区的html
this.text : 保存的是评论的textarea元素
this.cmtBtn : 保存的是提交按钮
this.noCmt : 保存的是没有评论时的文字提醒
this.cmtList : 保存的是列表的容器
this.loading : 保存的是加载列表时的loading GIF图片
this.pagerBox : 须要分页时的分页器容器
js上没有难点,都是一些jQuery的方法
this.parent.html(this.body)
这个没什么好讲的,很简单,这时咱们的评论组件应该在页面显示了,只是如今没有加载评论列表,也不能评论,下面先讲加载评论列表
首先是初始化列表,清空,显示加载gif图,隐藏没有评论的提醒字样,作好准备就发起ajax请求。
思路是用php将该评论区的留言所有弄下来,在前端再来整理,ajax请求为:
fn.resetList = function(){
this.loading.css('display', 'block')
this.noCmt.css('display', 'none');
this.cmtList.html('');
};
fn.getList = function(){
var self = this;
this.resetList();
$.ajax({
url: self.getCmtUrl,
type: 'get',
dataType: 'json',
data: { id: self.belong },
success: function(data){
if(!data){
alert('获取评论列表失败');
return !1;
};
//整理评论列表
self.initList(data);
self.loading.css('display', 'none');
//显示评论列表
if(self.lists.length == 0){
//暂时没有评论
self.noCmt.css('display', 'block');
}else{
//设置分页器
var total = Math.ceil(self.lists.length / self.offset);
self.pager = new Pager({
index: 1,
total: total,
parent: self.pagerBox[0],
onchange: self.doChangePage.bind(self),
label:{
prev: '<',
next: '>'
}
});
}
},
error: function(){
alert('获取评论列表失败');
}
});
};
get形式,而后传送id过去,获得了的数据但愿是列表数组。
php的内容不讲,下面贴出sql语句:
$id = $_GET['id'];
$query = "select * from comments where belong=$id order by time";
...
$str = '[';
foreach ($result as $key => $value) {
$id = $value['id'];
$username = $value['username'];
$time = $value['time'];
$content = $value['content'];
$parent = $value['parent'];
$str .= <<<end
{
"id" : "{$id}",
"parent" : "{$parent}",
"username" : "{$username.'",
"time" : "{$time}",
"content" : "{$content}",
"response" : []
}
end;
}
$str = substr($str, 0, -1);
$str .= ']';
echo $str;
得到的是json字符串,jQuery的ajax能够将它转为json数据,得到的数据以下:
若是加载成功,那么咱们获得的是一堆的数据,咱们如今是在success回调函数里,数据须要整理,才能显示,由于如今全部的评论回复都属于同一层。
fn.initList = function (data) {
this.lists = []; //保存评论列表
this.keys = {}; //保存评论id和index对应表
var index = 0;
//遍历处理
for(var i = 0, len = data.length; i < len; i++){
var t = data[i],
id = t['id'];
if(t['parent'] == 0){
this.keys[id] = index++;
this.lists.push(t);
}else{
var parentId = t['parent'],
parentIndex = this.keys[parentId];
this.lists[parentIndex]['response'].push(t);
}
};
};
个人思路就是:this.lists放的都是评论(parent为0的留言),经过遍历获取的数据,若是parent为0,就push进this.lists;不然parent不为0表示这是个回复,就找到对应的评论,把该回复push进那条评论的response中。
可是还有个问题,就是由于id是不断增加的,可能中间有些评论被删除了,因此id和index并不必定匹配,因此借助this.keys保存id和index的对应关系。
遍历一遍就能将全部的数据整理好,而且所有存在了this.lists中,接下来剩下的事情就是将数据变成html放进页面就行了。
//显示评论列表
if(self.lists.length == 0){
//暂时没有评论
self.noCmt.css('display', 'block');
}else{
//设置分页器
var total = Math.ceil(self.lists.length / self.offset);
self.pager = new Pager({
index: 1,
total: total,
parent: self.pagerBox[0],
onchange: self.doChangePage.bind(self),
label:{
prev: '<',
next: '>'
}
});
}
这是刚才ajax,success回调函数的一部分,这是在整理完数据后,若是数据为空,那么就显示“暂时没有评论”。
不然,就设置分页器,分页器我直接用了以前封装的,若是有兴趣能够看看我以前的博客:
简单说就是会执行一遍onchange函数,默认页数为1,保存在参数obj.index中
fn.doChangePage = function (obj) {
this.showList(obj.index);
};
fn.showList = (function(){
/* 生成一条评论字符串 */
function oneLi(_obj){
var str1 = '';
//处理回复
for(var i = 0, len = _obj.response.length; i < len; i++){
var t = _obj.response[i];
t.content = t.content.replace(/\<\;/g, '<');
t.content = t.content.replace(/\>\;/g, '>');
str1 += '<li class="f-clear"><table><tbody><tr><td>' +
'<span class="username">' + t.username + ':</span></td><td>' +
'<span class="child-content">' + t.content + '</span></td></tr></tbody></table>' +
'</li>'
}
//处理评论
var headImg = '';
if(_obj.username == "kang"){
headImg = 'kang_head.jpg';
}else{
var index = Math.floor(Math.random() * 6) + 1;
headImg = 'head' + index + '.jpg'
}
_obj.content = _obj.content.replace(/\<\;/g, '<');
_obj.content = _obj.content.replace(/\>\;/g, '>');
var str2 = '<li class="f-clear">' +
'<div class="head g-col-1">' +
'<img src="./img/head/' + headImg + '" width="100%"/>' +
'</div>' +
'<div class="content g-col-19">' +
'<div class="f-clear">' +
'<span class="username f-float-left">' + _obj.username + '</span>' +
'<span class="time f-float-left">' + _obj.time + '</span>' +
'</div>' +
'<span class="parent-content">' + _obj.content + '</span>' +
'<ul class="child-comment">' + str1 + '</ul>' +
'</div>' +
'<div class="respone-box g-col-2 f-float-right">' +
'<a href="javascript:void(0);" class="f-show response" data-id="' + _obj.id + '">[回复]</a>' +
'</div>' +
'</li>';
return str2;
};
return function (page) {
var len = this.lists.length,
end = len - (page - 1) * this.offset,
start = end - this.offset < 0 ? 0 : end - this.offset,
current = this.lists.slice(start, end);
var cmtList = '';
for(var i = current.length - 1; i >= 0; i--){
var t = current[i],
index = this.keys[t['id']];
current[i]['index'] = index;
cmtList += oneLi(t);
}
this.cmtList.html(cmtList);
};
})();
这个函数的参数为page,就是页数,咱们根据页数,截取this.lists的数据,而后遍历生成html。
html模板我是用字符串链接起来的,看我的喜爱。
生成后就 this.cmtList.html(cmtList);这样就显示列表了,效果图看最开始。
如今须要的功能还有评论回复,而init函数中也只剩下最后一个initEvent
fn.initEvent = function () {
//提交按钮点击
this.cmtBtn.on('click', this.addCmt.bind(this, this.cmtBtn, this.text, 0));
//点击回复,点击取消回复,点击回复中的提交评论按钮
this.cmtList.on('click', this.doClickResponse.bind(this));
};
上面截图来自个人我的网站,当咱们点击回复时,咱们但愿能有地方写回复,能够提交,能够取消,因为这几个元素都是后来添加的,因此咱们将行为都托管到评论列表这个元素。
下面先将提交评论事件函数。
fn.addCmt = function (_btn, _text, _parent) {
//防止屡次点击
if(_btn.attr('data-disabled') == 'true') {
return !1;
}
//处理提交空白
var value = _text.val().replace(/^\s+|\s+$/g, '');
value = value.replace(/[\r\n]/g,'<br >');
if(!value){
alert('内容不能为空');
return !1;
}
//禁止点击
_btn.attr('data-disabled','true');
_btn.html('评论提交中...');
//提交处理
var self = this,
email, username;
username = $.cookie('user');
if (!username) {
username = '游客';
}
email = $.cookie('email');
if (!email) {
email = 'default@163.com';
}
var now = new Date();
$.ajax({
type: 'get',
dataType: 'json',
url: this.setCmtUrl,
data: {
belong: self.belong,
parent: _parent,
email: email,
username: username,
content: value
},
success: function(_data){
//解除禁止点击
_btn.attr('data-disabled', '');
_btn.html('提交评论');
if (!_data) {
alert('评论失败,请从新评论');
return !1;
}
if (_data['result'] == 1) {
//评论成功
alert('评论成功');
var id = _data['id'],
time = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + ' ' +
now.getHours() + ':' + now.getMinutes() + ':' + now.getSeconds();
if (_parent == 0) {
var index = self.lists.length;
if (!self.pager) {
//设置分页器
self.noCmt.css('display', 'none');
var total = Math.ceil(self.lists.length / self.offset);
self.pager = new Pager({
index: 1,
total: total,
parent: self.pagerBox[0],
onchange: self.doChangePage.bind(self),
label:{
prev: '<',
next: '>'
}
});
}
self.keys[id] = index;
self.lists.push({
"id": id,
"username": username,
"time": time,
"content": value,
"response": []
});
self.showList(1);
self.pager._$setIndex(1);
}else {
var index = self.keys[_parent],
page = self.pager.__index;
self.lists[index]['response'].push({
"id": id,
"username": username,
"time": time,
"content": value
});
self.showList(page);
}
self.text.val('');
} else {
alert('评论失败,请从新评论');
}
},
error: function () {
alert('评论失败,请从新评论');
//解除禁止点击
_btn.attr('data-disabled', '');
_btn.html('提交评论');
}
});
}
参数有3个:_btn, _text, _parent 之因此要有这三个参数是由于评论或者回复这样才能使用同一个函数,从而不用分开写。
点击后就是常见的防止屡次提交,检查一下cookie中有没有username、email等用户信息,没有就使用游客身份,而后处理一下内容,去去掉空白啊,\n换成 <br> 等等,检验事后发起ajax请求。
成功后把新的评论放到this.lists,而后执行this.showList(1)刷新显示
php部分仍然不讲,sql语句以下:
$parent = $_GET['parent'];
$belong = $_GET['belong'];
$content = htmlentities($_GET['content']);
$username = $_GET['username'];
$email = $_GET['email'];
$query = "insert into comments (parent,belong,content,time,username,email) value ($parent,$belong,'$content',NOW(),'$username','$email')";
fn.doClickResponse = function(_event){
var target = $(_event.target);
var id = target.attr('data-id');
if (target.hasClass('response') && target.attr('data-disabled') != 'true') {
//点击回复
var oDiv = document.createElement('div');
oDiv.className = 'cmt-form';
oDiv.innerHTML = '<textarea class="cmt-text" placeholder="欢迎建议,提问题,共同窗习!"></textarea>' +
'<button class="u-button resBtn" data-id="' + id + '">提交评论</button>' +
'<a href="javascript:void(0);" class="cancel">[取消回复]</a>';
target.parent().parent().append(oDiv);
oDiv = null;
target.attr('data-disabled', 'true');
} else if (target.hasClass('cancel')) {
//点击取消回复
var ppNode = target.parent().parent(),
oRes = ppNode.find('.response').eq(0);
target.parent().remove();
oRes.attr('data-disabled', '');
} else if (target.hasClass('resBtn')) {
//点击评论
var oText = target.parent().find('.cmt-text').eq(0),
parent = target.attr('data-id');
this.addCmt(target, oText, parent);
}else{
//其余状况
return !1;
}
};
根据target.class来判断点击的是哪一个按钮。
若是点击回复,生成html,放到这条评论的后面
var oDiv = document.createElement('div');
oDiv.className = 'cmt-form';
oDiv.innerHTML = '<textarea class="cmt-text" placeholder="欢迎建议,提问题,共同窗习!"></textarea>' +
'<button class="u-button resBtn" data-id="' + id + '">提交评论</button>' +
'<a href="javascript:void(0);" class="cancel">[取消回复]</a>';
target.parent().parent().append(oDiv);
oDiv = null;
target.attr('data-disabled', 'true'); //阻止重复生成html
点击取消,就把刚才生成的remove掉
var ppNode = target.parent().parent(),
oRes = ppNode.find('.response').eq(0);
target.parent().remove();
oRes.attr('data-disabled', ''); //让回复按钮从新能够点击
点击提交,获取一下该获取的元素,直接调用addCmt函数
var oText = target.parent().find('.cmt-text').eq(0),
parent = target.attr('data-id');
this.addCmt(target, oText, parent);
注意: parent刚才生成html时我把它存在了提交按钮的data-id上了。
到此所有功能都实现了
代码下载:百度云盘