## 快速入門五、數據操作及Ajax
前面的教程我們已經完成了一個留言本的初步開發。接下來我們來了解一下sp框架的數據操作,以及初步學習一下Ajax的開發方式。
> Ajax是異步Javascript技術,可以在頁面沒有刷新的情況下,從服務器接收新的內容來顯示和操作。
> 時至今日,Ajax已經成為了網站開發技術的標配。
[本教程最終完成例子下載](images/5.zip)
### 一、頁面調整
我們的留言本由于需要加入更多的操作,所以在頁面HTML上面需要再修改一下,加入一個留言查看的彈窗。
新的HTML在這里:[下載新的頁面](images/4.zip)
解壓取出里面的showmsg.html文件,它只是頁面的HTML片段,主要提供顯示一個查看單條留言的對話框,并不是完整的頁面。
將showmsg.html放到protected/view目錄下面。
我們現在需要把這個頁面插入到guestbook.html中。打開guestbook.html,在最下面填寫留言表單的下方,我們加入一行代碼:
<{include file="showmsg.html"}>
看起來大概是這樣:

刷新localhost后,右鍵查看源代碼,我們會發現showmsg.html的內容已經放在include那個位置上了。
> 這是sp框架模板引擎的include語法功能,具體了解可以參考手冊相關內容。
> inlucde語法的好處是可以把模板內通用的HTML片段抽離出來獨立成文件,方便其他模板文件引用進去,達到一次修改就可以改變所有引用的內容。
### 二、查看單條留言
我們在protected/controller目錄下面,新建個php文件,名稱是:ViewController.php,里面內容是:
<?php
class ViewController extends BaseController {
function actionShow(){
// 把提交的upid參數,作為值來構造一個查詢條件數組,
// 條件是:把id字段中等于提交upid參數值的記錄查詢出來
$condition = array("id" => arg("upid"));
$guestbook = new Model("guestbook");
// 通過find()方法,按條件數據進行查詢,
// find返回結果是對應記錄的一維數組
$result = $guestbook->find($condition);
// 輸出看看
dump($result);
}
}
當我們打開瀏覽器http://localhost/view/show.html?upid=1 ,可以看到:

內容比較多,我們仔細來研究研究:
- 這里是ViewController類,跟MainController一樣,它繼承于BaseController類,而且類的名字跟文件名是一樣的。
- ViewController類里面的方法叫actionShow(),action開頭的方法是可以被瀏覽器訪問到的。actionShow()是執行這塊操作的主體代碼塊。
- actionShow()方法開始我們把提交上來的參數upid,作為值構造成了一個查詢條件的數組,數組名稱是$condition。
- arg("upid")代表獲取瀏覽器提交上來upid的參數。
- 繼續實例化guestbook表,然后調用find()方法,用$condition這個條件數組作為參數輸入。
- 取得find()方法的結果$result,并且用dump()進行調試輸出觀察。
Model類的find()方法,指的是查詢一條符合條件的記錄,返回的是一維數組。
對比一下前章節學習過的findAll()方法,我們把上述代碼的find()直接改成findAll()。

刷新瀏覽器我們可以看到跟find()的一點不一樣的地方了:

是的,findAll()比find()的結果多了個0,也就是多了一個維度。
> 從原理上來講,find()方法內部其實是調用了findAll()來查詢,但是會指定只返回第一條記錄,然后find()會把第一條記錄變成一維的數組再返回給用戶。
也就是說,find()實際上是findAll()的第一條記錄,不過find()相對方便的是它是一維數組,可以直接通過類似$result["title"]的方式來取得值,而不是findAll()結果的$result[0]["title"]。
> 相同條件下,find()的$result["title"]是等于findAll()的$result[0]["title"]的。
這時候我們已經可以指定查詢到了單條留言的內容了。那么我們會把返回變成json格式。
> json格式是目前較為通用的數據傳輸格式。
我們現在把dump()調試去掉,改成json輸出:
<?php
class ViewController extends BaseController {
function actionShow(){
// 把提交的upid參數,作為值來構造一個查詢條件數組,
// 條件是:把id字段中等于提交upid參數值的記錄查詢出來
$condition = array("id" => arg("upid"));
$guestbook = new Model("guestbook");
// 通過find()方法,按條件數據進行查詢,
// find返回結果是對應記錄的一維數組
$result = $guestbook->findAll($condition);
// 輸出看看
header('Content-type: application/json');
echo json_encode($result);
}
}
- header()函數的作用是輸出后讓瀏覽器識別這是一個json流。
- json_encode()函數是php自帶函數,可以將php數組轉換成json格式,這里轉換好了之后,就echo輸出了。
> 跟json_encode()對應的還有json_decode()函數,作用相反,將json格式轉回php數組。
> echo是輸出的意思,比如我們最早的 echo "hello world";,當然我們這個頁面是沒有用到模板的(沒有display),是直接輸出的。
刷新瀏覽器我們可以看到json的輸出了:

這里看起來像是亂碼的東西,就是json格式的數據。
---
接下來我們修改一下HTML,對,就是插入那個showmsg.html
<div class="modal fade" id="showModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="show_title"></h4>
</div>
<div class="modal-body">
<p id="show_contents"></p>
<blockquote class="blockquote-reverse small">
<ul class="list-inline text-muted">
<li>by</li>
<li id="show_username"></li>
<li id="show_createtime"></li>
<li>
<button type="button" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> 100</button>
</li>
</ul>
</blockquote>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
</div>
</div>
</div>
</div>
<script>
function showmsg(id){
// 由于需要JSON數據,所以使用$.getJSON
$.getJSON("<{url c="view" a="show"}>", { 'upid': id }, function(json){
// 這里是返回的數據,json變量就是json格式的數據,可以直接使用
// 給各個位置賦值上去
$("#show_title").html(json.title);
$("#show_contents").html(json.contents);
$("#show_username").html(json.username);
$("#show_createtime").html(json.createtime);
// 顯示對話框
$('#showModal').modal('show');
});
}
</script>
我們修改了不少地方,逐個來看看。
- 在每個需要顯示內容的HTML元素上,我們加入了id的屬性,也就是分別給它們取個名字,方便通過JS來進行賦值。
- 加入了一個JS函數showmsg(),這個js函數的作用首先是調用jQuery庫的getJSON()方法,把傳入的id發到<{url c="view" a="show"}>這個地址上面去。
- 然后接收返回的json數據,一一對應填入HTML里面。
- 最后把對話框顯示出來。
> JS是Javascript的簡稱,JS是可以在網頁上面執行的腳本程序。具體JS教程可以參考w3c的教程。
> 由于本身頁面模板上面已經有jQuery庫,故不再需要引入可以直接使用。jQuery是JS的一個流行的類庫,非常有用,建議多學習。
我們還需要給每個留言增加個啟動showmsg()函數的鏈接。在guestbook.html里面,我們把循環里面的標題增加一個鏈接。
把原來的:
<h4 class="media-heading"><{$r.title}><button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></h4>
改成了:
<h4 class="media-heading"><a href="javascript:void(0);" onclick="showmsg(<{$r.id}>)"><{$r.title}></a><button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button></h4>
- onclick是HTML的一個事件,意思是點擊后發生點什么,這里的代碼指的是“當點擊這個鏈接時,指向地址是空的,但是會啟動JS函數showmsg()”
- showmsg()函數傳入了當前留言的id值。
保存,刷新瀏覽器。點擊任何一條留言的標題,那么就可以看到:

這里彈出的內容,是通過Ajax從view/show里面獲取到的。
### 三、點贊
看到每個留言下面的大拇指標志嗎? 通常在網絡上這是點贊的意思。我們來實現這樣的功能吧。
點贊通常是一個數字,所以我們需要在原來的數據表guestbook上面新增一個數字字段來存儲它,名字就叫dig。這里是通過PHPMyAdmin增加的界面:

對應的SQL語句是:
ALTER TABLE `guestbook` ADD `dig` INT(11) NULL DEFAULT '0' AFTER `username`;
完成后我們可以看到dig字段已經在那兒了。

打開ViewController.php,增加一個action作為點贊操作的方法。
function actionDig(){
// 同樣的,首先是根據提交參數確定條件
$condition = array("id" => arg("upid"));
$guestbook = new Model("guestbook");
// 先查出當前記錄的點贊數是多少
$result = $guestbook->find($condition);
// 然后把點贊加一
$dig = $result["dig"] + 1;
// 建新的更新數據,跟create相似的
// 是需要更新的字段名對應新的值
$newrow = array("dig" => $dig);
// 執行更新操作
// 這里update的意思是把符合$condition條件的記錄
// 按$newrow設定的字段,把這些字段的值改成新值
$guestbook->update($condition, $newrow);
// 然后直接顯示更新后的點贊數
echo $dig;
}
- 首先我們是查詢出來對應的id記錄的dig數量是多少,然后加一。
- 再通過update把加一后的值更新回去
- 最后顯示一下新的值
update()方法輸入兩個參數,前面參數是條件數組,后面參數是需要更新的字段+值。
> 從使用方式來看,update()方法很像是find() + create()的組合。
然后在guestbook.html頁面上面,我們修改一下點贊顯示的方式,加入onclick事件。
> 因為點贊顯示的位置沒有元素,所以我們可以用span把數字包裹起來,這樣就可以給它賦以名稱id了。
<button type="button" class="btn btn-default btn-xs" onclick="digmsg(<{$r.id}>)"><span class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></span> <span id="dig-<{$r.id}>"><{$r.dig}></span></button>
首先我們刷新一下頁面,會發現點贊數已經出來了——當然只有0。
然后我們在showmsg.html的script里面,再加上digmsg()函數:
function digmsg(id){
// 這里不需要json返回,所以直接使用$.get即可
$.get("<{url c="view" a="dig"}>", {"upid" : id}, function(dig){
$("#dig-" + id).html(dig);
});
}
這里我們有個小技巧,就是在頁面foreach循環的時候,點贊位置的id是dig-<{$r.id}>,所以每條留言的這個id都是不一樣的,所以在 $("#dig-" + id).html(dig);就可以把返回的數據填充到正確id的點贊位置了。
完成后我們刷新一下瀏覽器,發現點贊數字可以點擊了,并且點擊后,數字會變大。

最后我們還需要修改一下查看留言的彈出框,把點贊數也顯示上去。
包括把彈出的HTML點贊位置改為:
<span id="show_dig"></span>
JS函數showmsg()里面也要增加:
$("#show_dig").html(json.dig);
改好后彈窗框也OK了:

### 四、時間顯示的補充
從上圖彈出框可以看到留言時間還是時間戳。我們來把它修改一下成好看的日期吧。
在ViewController的actionShow()方法里面,我們加入一個轉換:
$result["createtime"] = date("Y年m月d日 H:i:s", $result["createtime"]);
放在查詢到$result后,還沒有輸出之前。大概的位置是:

那么彈出框時間也就正常了。
> 這里介紹的技巧,是當查詢到數據之后,其實還是可以進一步修改的,有時候我們在php端修改,會更方便點。
### 五、刪除留言
一般數據庫操作,最基本的就是增刪改查(CURD),分別是增加(create)、刪除(delete),修改(update),查詢(find/findAll)。增改查前面已經介紹過了,我們現在來介紹最后一個刪除操作。
> CURD的查詢是英文READ,但是我們比較喜歡用find/findAll,這是文法的問題。
打開ViewController.php,增加一個action作為刪除操作的方法。
function actionDel(){
// 同樣的,首先是根據提交參數確定條件
$condition = array("id" => arg("upid"));
$guestbook = new Model("guestbook");
// 直接返回結果,實際上這個結果是被忽略的
echo $guestbook->delete($condition);
}
刪除操作相對簡單,跟其他操作一樣,先是確定數組條件,然后直接調用delete()方法。
delete()方法只有一個參數,代表了刪除記錄的條件。
接下來我們修改guestbook.html文件,首先我們把foreach循環留言的大塊DIV,加入一個ID,以識別不同的留言。
<div class="panel panel-default" id="panel-<{$r.id}>">
然后在每個留言的右上角,已經有個 x 了,這是一個刪除按鈕。我們加入事件給它。

代碼是:
<button type="button" class="close" aria-label="Close" onclick="delmsg(<{$r.id}>)"><span aria-hidden="true">×</span></button>
具體部分代碼是:
...
<div class="col-md-8">
<{foreach $records as $r}>
<div class="panel panel-default" id="panel-<{$r.id}>">
<div class="panel-body">
<div class="media">
<div class="media-left">
<a href="#">
<img class="media-object" src="/i/img/1.gif" alt="...">
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="javascript:void(0);" onclick="showmsg(<{$r.id}>)"><{$r.title}></a><button type="button" class="close" aria-label="Close" onclick="delmsg(<{$r.id}>)"><span aria-hidden="true">×</span></button></h4> <{$r.contents}>
<blockquote class="blockquote-reverse small">
...
然后我們還是在showmsg.html的script里面,再加上delmsg()函數:
function delmsg(id){
// 這里不需要json返回,所以直接使用$.get即可
$.get("<{url c="view" a="del"}>", {"upid" : id}, function(){
$("#panel-" + id).fadeOut();
});
}
- 這里實際上不需要返回值,所以也沒有賦值給返回值。
- fadeOut()是一個很好玩的動畫效果,會讓頁面上的元素漸變消失。
OK后我們來刷新一下頁面,點擊其中一個留言右上角的叉,留言就消失了。
### 六、分頁顯示
當我們留言內容變多的時候,頁面會變得很長,這時候我們需要對留言進行分頁顯示。
> 分頁也是比較基礎的功能之一,大部分網站都可以見到它的身影。
> 另外在我們的頁面上,其實也是有分頁的HTML在的。
我們先來介紹一下關于findAll()方法的深入使用方法。
findAll()方法有四個參數,依次是$conditions,$sort,$fields,$limit;
- $conditions是條件數組,默認情況下是字段對應值的查詢條件。
- $sort是排序方式,比如說接下來我們會用到“createtime DESC”就是根據createtime創建時間倒序查詢。
- $fields是查詢出來的字段,一般是“*”即可。
- $limit是限定查詢的數量,也是我們分頁的基礎。默認情況是“第幾條,要幾條”,比如說“10, 20”指的是從第10條記錄開始,查出20條數據。
除了限定條數,$limit還有一種分頁的方法:數組形式的$limit是自動分頁的功能,指的是array(第幾頁, 每頁多少條);如如 $limit = array(10, 20);,就是查出第10頁,每頁20條記錄。
> “查出第10頁,每頁20條記錄”,我們可以知道實際上findAll出來的結果最多是20條。
打開MainController文件,我們修改一下actionIndex()方法。
function actionIndex(){
// 接收頁碼參數
$page = (int)arg("p", 1);
// 實例化一個guestbook的模型類
$guestbook = new Model("guestbook");
// 用findAll()方法查詢guestbook表的全部數據
$this->records = $guestbook->findAll(null, "createtime DESC", "*", array($page, 3));
// 輸出看看
// dump($this->records);
$this->pager = $guestbook->page;
// dump($this->pager);
$this->display("guestbook.html");
}
改后我們發現頁面上只有3篇留言了。下面我們來研究研究:
**頁碼**
- 接收頁碼$page,我們用了arg("p", 1);后面參數1是代表如果p不存在,則返回1.
- (int)arg("p", 1)的意思是強制將p參數轉換成數字,保證數據庫安全。
**findAll**
$guestbook->findAll(null, "createtime DESC", "*", array($page, 3));
這里用了findAll的四個參數:
- null指的是沒有條件,查詢全部內容。
- "createtime DESC"指的是按createtime倒序查詢,如果是正序那么應該是"createtime ASC"。
- "*"是全部字段,默認這個就好。
- array($page, 3),這里指的是:當前是第$page頁,每頁3條留言。
綜上我們可以知道,現在$this->records查詢出來的結果是第$page的3條留言。
> 因為我本機上面留言較少,所以分頁只設置了3頁,如果大家有興趣可以設置大點的數字。
**page**
$this->pager = $guestbook->page;
這句代碼緊跟著上面的findAll查詢,意思是從$guestbook里面取出上一次findAll給出來的頁碼數據。
> 這句代碼必須緊跟著findAll,如果$guestbook再來一次findAll,那么可能$guestbook->page就不是第一次findAll的頁碼數據了。
$this->pager輸出看看:

字段 | 含義
--- | ---
total_count | 符合條件的總記錄數量
page_size | 每頁多少條件記錄
total_page | 總共有多少頁
first_page | 第一頁頁碼
prev_page | 上一頁頁碼
next_page | 下一頁頁碼
last_page | 最后一頁頁碼
current_page | 當前頁頁碼
all_pages | 全部頁碼數組
offset | 查詢位移,等于$limit的第一個參數
limit | 查詢條數,等$limit的第二個參數
取得頁碼數據后,我們就可以開始修改頁面上的頁面顯示了。找到guestbook.html文件的約55行左右,把分頁顯示的nav整個修改成:
<{if $pager}>
<nav>
<ul class="pagination pull-right">
<li>
<a href="<{url c="main" a="index" p=$pager.prev_page}>" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<{foreach $pager.all_pages as $p}>
<li<{if $p == $pager.current_page}> class="active"<{/if}>>
<a href="<{url c="main" a="index" p=$p}>"><{$p}></a>
</li>
<{/foreach}>
<li>
<a href="<{url c="main" a="index" p=$pager.next_page}>" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
<{/if}>
- 首先我們判斷是否有分頁數據,這里<{if}>標簽的作用就是如果$pager為空,則不會顯示整個分頁。$pager為空的意思是數據表記錄沒有能填滿一頁(根據分多少頁的參數)。
- 填充上一頁和下一頁,注意是通過url函數傳遞p參數的
- 循環all_pages的頁碼數據,顯示各個頁碼。
- 循環過程中,會通過<{if}>做一下判斷,判斷顯示的頁碼是否當前頁,如果是的話,會在li上面加入一個 class="active"的屬性,加亮顯示。
- 最終分頁上面會生成各個分頁的鏈接。
> 之所以未滿一頁不顯示分頁,這也是對頁面瀏覽者友好的表現,如果只有一兩個留言,那么顯示個點擊不了的頁碼,甚不美觀。
我們來看看最終效果:

---
以上我們已經了解過簡單數據操作和Ajax等相關知識,sp框架的初級內容已經介紹得差不多了。請大家繼續學習我們的教程,以便了解更多相關知識。
- 自述
- 一、入門教程
- 1. 開始使用SpeedPHP
- 2. Hello World
- 3. 理解MVC
- 4. 制作留言本
- 5. 數據操作及Ajax
- 二、框架概述
- 1. 特色
- 2. 版權及開源協議
- 3. 開發環境
- 4. 編碼版本
- 5. SAE平臺使用
- 三、開發指南
- 1. 開發流程
- 2. 架構及擴展
- 3. 程序目錄結構
- 4. 命名建議
- 5. 安全建議
- 6. 用戶自定義
- 7. 模塊modules
- 四、訪問交互
- 1. 表單提交及數據獲取
- 2. session/cookie的使用
- 3. 偽靜態及URL跳轉
- 4. 使用frameset
- 5. 模板引擎特性和使用方法
- 五、數據操作
- 1. 建立數據模型類
- 2. 數據操作教程
- 3. 分頁
- 4. SQL支持及關聯實現
- 5. 多數據庫、主從庫配置