個人網(wǎng)站留言頁面(前端jQuery編寫、后臺php讀寫MySQL)
首先,上個人網(wǎng)站的留言頁面,大家可以看看效果:留言板
前端為了省事,使用jQuery編寫,后臺使用php簡單讀寫MySQL數(shù)據(jù)庫。
數(shù)據(jù)庫設(shè)計和實現(xiàn)思路
數(shù)據(jù)庫創(chuàng)建了一個表:comments,結(jié)構(gòu)如下圖:
全部評論(包括文章評論回復(fù),留言板)都寫在同一張表中,不同的評論區(qū)用字段belong區(qū)分
同一個評論區(qū)里,parent為0表示為評論,parent為某值時表示為哪個評論的回復(fù),思路不復(fù)雜。
注意,這里并不講CSS,大家根據(jù)自己的需要定制,現(xiàn)在開始封裝:
定下功能
我們根據(jù)自己的需要定下功能,首先我的網(wǎng)站并沒有實現(xiàn)消息提醒,即時通訊的功能,所以評論回復(fù)并不會提示站長或者用戶,只會對留言區(qū)產(chǎn)生效果,所以我們只要簡單實現(xiàn)以下功能:
1、顯示評論列表
2、能夠提交評論
3、進行回復(fù)
評論類
我們將評論的功能封裝成一個類,通過實例化就能創(chuàng)建不同的評論區(qū),所以不難想到,
實例化的時候我們需要傳入的參數(shù)可能有:評論區(qū)的id、獲取評論的php地址,提交評論的php地址。
所以我們可以猜想實例化評論區(qū)的代碼可能為:
var oCmt = new Comment({ parent: $('#box'), //你想要將這個評論放到頁面哪個元素中 id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
當(dāng)然,我是在Comment類上定義一個靜態(tài)方法
Comment.allocate({ parent: $('#box'), id: 0, getCmtUrl: './php/getcomment.php', setCmtUrl: './php/comment.php' })
大同小異,只是初始化的地方不同而已
構(gòu)造函數(shù)
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;
變量先不說,像我都是先寫功能函數(shù),然后需要添加屬性變量再回頭來添加,我們只需要看到構(gòu)造函數(shù)最后執(zhí)行了:
this.init(options)
從名字可以看出是初始化函數(shù)。
init函數(shù)
fn.init = function (options) { //初始化node this.initNode(options); //將內(nèi)容放進容器 this.parent.html(this.body); //初始化事件 this.initEvent(); //獲取列表 this.getList(); };
fn為Comment.prototype,只說一次,下面就不再說了。
初始化就是有4個工作要做,從代碼注釋可以看出,現(xiàn)在一個一個講解
initNode函數(shù)
從名字可以看出主要初始化節(jié)點或者緩存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="歡迎建議,提問題,共同學(xué)習(xí)!"></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 : 保存的是容器節(jié)點
this.body : 保存的是評論區(qū)的html
this.text : 保存的是評論的textarea元素
this.cmtBtn : 保存的是提交按鈕
this.noCmt : 保存的是沒有評論時的文字提醒
this.cmtList : 保存的是列表的容器
this.loading : 保存的是加載列表時的loading GIF圖片
this.pagerBox : 需要分頁時的分頁器容器
js上沒有難點,都是一些jQuery的方法
將內(nèi)容放進容器中
this.parent.html(this.body)
這個沒什么好講的,很簡單,這時我們的評論組件應(yīng)該在頁面顯示了,只是現(xiàn)在沒有加載評論列表,也不能評論,下面先講加載評論列表
getList 函數(shù)
首先是初始化列表,清空,顯示加載gif圖,隱藏沒有評論的提醒字樣,做好準備就發(fā)起ajax請求。
思路是用php將該評論區(qū)的留言全部弄下來,在前端再來整理,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{ //設(shè)置分頁器 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過去,得到了的數(shù)據(jù)希望是列表數(shù)組。
php的內(nèi)容不講,下面貼出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可以將它轉(zhuǎn)為json數(shù)據(jù),獲得的數(shù)據(jù)如下:
如果加載成功,那么我們得到的是一堆的數(shù)據(jù),我們現(xiàn)在是在success回調(diào)函數(shù)里,數(shù)據(jù)需要整理,才能顯示,因為現(xiàn)在所有的評論回復(fù)都屬于同一層。
initList 函數(shù)
fn.initList = function (data) { this.lists = []; //保存評論列表 this.keys = {}; //保存評論id和index對應(yīng)表 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的留言),通過遍歷獲取的數(shù)據(jù),如果parent為0,就push進this.lists;否則parent不為0表示這是個回復(fù),就找到對應(yīng)的評論,把該回復(fù)push進那條評論的response中。
但是還有個問題,就是因為id是不斷增長的,可能中間有些評論被刪除了,所以id和index并不一定匹配,所以借助this.keys保存id和index的對應(yīng)關(guān)系。
遍歷一遍就能將所有的數(shù)據(jù)整理好,并且全部存在了this.lists中,接下來剩下的事情就是將數(shù)據(jù)變成html放進頁面就好了。
//顯示評論列表 if(self.lists.length == 0){ //暫時沒有評論 self.noCmt.css('display', 'block'); }else{ //設(shè)置分頁器 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回調(diào)函數(shù)的一部分,這是在整理完數(shù)據(jù)后,如果數(shù)據(jù)為空,那么就顯示“暫時沒有評論”。
否則,就設(shè)置分頁器,分頁器我直接用了之前封裝的,如果有興趣可以看看我之前的文章:
面向?qū)ο螅悍猪撈鞣庋b
簡單說就是會執(zhí)行一遍onchange函數(shù),默認頁數(shù)為1,保存在參數(shù)obj.index中
fn.doChangePage = function (obj) { this.showList(obj.index); };
showList函數(shù)
fn.showList = (function(){ /* 生成一條評論字符串 */ function oneLi(_obj){ var str1 = ''; //處理回復(fù) 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 + '">[回復(fù)]</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); }; })();
這個函數(shù)的參數(shù)為page,就是頁數(shù),我們根據(jù)頁數(shù),截取this.lists的數(shù)據(jù),然后遍歷生成html。
html模板我是用字符串連接起來的,看個人喜好。
生成后就 this.cmtList.html(cmtList);這樣就顯示列表了,效果圖看最開始。
現(xiàn)在需要的功能還有評論回復(fù),而init函數(shù)中也只剩下最后一個initEvent
initEvent 函數(shù)
fn.initEvent = function () { //提交按鈕點擊 this.cmtBtn.on('click', this.addCmt.bind(this, this.cmtBtn, this.text, 0)); //點擊回復(fù),點擊取消回復(fù),點擊回復(fù)中的提交評論按鈕 this.cmtList.on('click', this.doClickResponse.bind(this)); };
上面截圖來自我的個人網(wǎng)站,當(dāng)我們點擊回復(fù)時,我們希望能有地方寫回復(fù),可以提交,可以取消,由于這幾個元素都是后來添加的,所以我們將行為都托管到評論列表這個元素。
下面先將提交評論事件函數(shù)。
addCmt 函數(shù)
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('內(nèi)容不能為空'); 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) { //設(shè)置分頁器 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('提交評論'); } }); }
參數(shù)有3個:_btn, _text, _parent 之所以要有這三個參數(shù)是因為評論或者回復(fù)這樣才能使用同一個函數(shù),從而不用分開寫。
點擊后就是常見的防止多次提交,檢查一下cookie中有沒有username、email等用戶信息,沒有就使用游客身份,然后處理一下內(nèi)容,去去掉空白啊,\n換成 <br> 等等,檢驗過后發(fā)起ajax請求。
成功后把新的評論放到this.lists,然后執(zhí)行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')"; doClickResponse 函數(shù) fn.doClickResponse = function(_event){ var target = $(_event.target); var id = target.attr('data-id'); if (target.hasClass('response') && target.attr('data-disabled') != 'true') { //點擊回復(fù) var oDiv = document.createElement('div'); oDiv.className = 'cmt-form'; oDiv.innerHTML = '<textarea class="cmt-text" placeholder="歡迎建議,提問題,共同學(xué)習(xí)!"></textarea>' + '<button class="u-button resBtn" data-id="' + id + '">提交評論</button>' + '<a href="javascript:void(0);" class="cancel">[取消回復(fù)]</a>'; target.parent().parent().append(oDiv); oDiv = null; target.attr('data-disabled', 'true'); } else if (target.hasClass('cancel')) { //點擊取消回復(fù) 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; } };
根據(jù)target.class來判斷點擊的是哪個按鈕。
如果點擊回復(fù),生成html,放到這條評論的后面
var oDiv = document.createElement('div'); oDiv.className = 'cmt-form'; oDiv.innerHTML = '<textarea class="cmt-text" placeholder="歡迎建議,提問題,共同學(xué)習(xí)!"></textarea>' + '<button class="u-button resBtn" data-id="' + id + '">提交評論</button>' + '<a href="javascript:void(0);" class="cancel">[取消回復(fù)]</a>'; target.parent().parent().append(oDiv); oDiv = null; target.attr('data-disabled', 'true'); //阻止重復(fù)生成html
點擊取消,就把剛才生成的remove掉
var ppNode = target.parent().parent(), oRes = ppNode.find('.response').eq(0); target.parent().remove(); oRes.attr('data-disabled', ''); //讓回復(fù)按鈕重新可以點擊
點擊提交,獲取一下該獲取的元素,直接調(diào)用addCmt函數(shù)
var oText = target.parent().find('.cmt-text').eq(0), parent = target.attr('data-id'); this.addCmt(target, oText, parent);
注意: parent剛才生成html時我把它存在了提交按鈕的data-id上了。
到此全部功能都實現(xiàn)了,希望對大家的學(xué)習(xí)有所啟發(fā)。
相關(guān)文章
js如何實現(xiàn)點擊標簽文字,文字在文本框出現(xiàn)
這篇文章主要介紹了js如何實現(xiàn)點擊標簽文字,文字在文本框出現(xiàn),感興趣的小伙伴們可以參考下2015-08-08javascript SocialHistory 檢查訪問者是否訪問過某站點
今天delicious上這個名為 SocialHistory 的腳本十分引人注目。源代碼可以在這里下載。這段js代碼的功能就是判斷你的用戶有沒有訪問過某個網(wǎng)站。使用方法很簡單,例如:2008-08-08