首先,上我的網站的留言頁面,你們能夠看看效果:留言板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上了。
到此所有功能都實現了
代碼下載:百度雲盤