之前自學 Laravel 4 的時候,分頁跟搜尋做的真是...亂七八糟,知道亂七八糟是因為,知道了有更好的寫法,不然就是會一直停留在自己的已知。
後來這兩個月接觸 Laravel 5 之後,發現有些以前的寫法,可以寫得更好,比方說... 有些現成的 method,我沒發現以前可以那樣用之類的情況,還去網路上找了一些沒有必要的方法,所以打算先一些做紀錄 XD,描述一下大概的邏輯,不會把整份 code 把拿出來講,如果有其他做法也歡迎讓我知道。
我這邊所指的分頁跟搜尋,是以一般後台所需要的功能為主,文章可能會牽涉 controller, model 跟 view,畢竟一個搜尋,除了 ORM 要處理帶入的搜尋條件之外,也要影響分頁的顯示,還有排序等等情況要做。改天有空,我會再分享一篇前端用 ajax 撈 json 的分頁方式。
我們先從 ORM 說起吧,在沒有分頁之前,撈資料,建 Model 都是稀鬆平常的事,最常見的撈法就是整個全撈出來,像這樣:
接下來,來做一點複雜的情況,假設在 article.view 加了一個搜尋用的 Form 表單,讓 user 可以搜尋文章的作者(Author),文章的標題(Title),文章的流水號(Id)。假設 user 送出了那個表單,應該發生什麼事情?
(1) 如果是用 GET method 送出表單,網址應該呈現 localhost/article?author=winwu&title=&id=&sort=id&order=desc
(2) view 得到的資料要是正確的
(3) 資料超過某個數量後,要顯示分頁
先來解決 (1),當搜尋的條件已經進到頁面時,ORM 就需要做一些改變,首先我會在 controller 決定我願意接受的搜尋條件參數: (簡單來說其實就是接 $_GET 參數啦! 只是我現在 focus 在 Laravel 上。)
(1) 的部分,算是到一個段落了。
接下來,處理 (2)。
view 就只有兩件事,兩件事都可以簡單,也可以複雜。
(2-1) 顯示分頁
(2-2) 列表的表格 tr th 上,要有排序的功能可以點按。
(2-1) 相對單純,pagination 只要看官方文件即可,顯示方式也跟 orm 那邊的設定有關,如果你是用 ->simplePaginate(10) 那就只會呈現只有上一頁, 下一頁的分頁顯示,如果是 ->paginate(10),就會顯示比較完整的分頁。
顯示分頁,要在 article.view 加上 {!! $articles->render() !!} ,就會顯示出分頁了,這就是 (3) 。
不過這裡有個問題要注意,當我已經有搜尋某個條件之後,點到分頁的第二頁,原本的 query string 並不會帶到第二頁去,網址只會變成 localhost/article?page=2 而不會是 localhost/article?author=ssss&title=&id=&sort=id&order=desc&page=2。
這怎麼辦呢,有個好用的 method 叫做 appends(),可以把當下頁面上的搜尋條件參數,帶到分頁 link 的 url 上,你可以參考文件的 Appending To Pagination Links,或是像我一樣操作在 ORM 上。
我加上了 appends 到 articles 上。
(2-2) 的話,排序 icon 大部份都是採用 bootstrap 的 icon 或是 font-awesome,麻煩就在這個排序的按鈕跟分頁其實是有些類似的,他一樣要帶入當下的搜尋條件,除了 order 跟 sort 要改變。
這個部分我至今也沒有很好的解法,但一直寫類似的 code,有些麻煩,後來我把這塊的 html 拆出去寫成兩個 function (這兩個 function 可以寫在 article.index 的 view 的最上方,好一點的做法就是拆出去一個 class 去包裝他 )
接著在排序的按鈕使用這個 function: (只舉某個欄位當作例子)
大概是這樣,有想到什麼我會再補充。
另外根據經驗,如果 ORM 需要 join 到三張表以上,會變得蠻慢的... QQ。
下週還會跟公司的架構師做一些 code 的調整,有什麼我覺得一定會修正的部分,我會再補上來 :P
後來這兩個月接觸 Laravel 5 之後,發現有些以前的寫法,可以寫得更好,比方說... 有些現成的 method,我沒發現以前可以那樣用之類的情況,還去網路上找了一些沒有必要的方法,所以打算先一些做紀錄 XD,描述一下大概的邏輯,不會把整份 code 把拿出來講,如果有其他做法也歡迎讓我知道。
我這邊所指的分頁跟搜尋,是以一般後台所需要的功能為主,文章可能會牽涉 controller, model 跟 view,畢竟一個搜尋,除了 ORM 要處理帶入的搜尋條件之外,也要影響分頁的顯示,還有排序等等情況要做。改天有空,我會再分享一篇前端用 ajax 撈 json 的分頁方式。
我們先從 ORM 說起吧,在沒有分頁之前,撈資料,建 Model 都是稀鬆平常的事,最常見的撈法就是整個全撈出來,像這樣:
$articles = Article::all();
這個全部撈出來的 $articles 變數,可能會放在 ArticleController 某個顯示列表的 method,我來假設那個 method 叫做 getIndex 好了,那麼 ArticleController 的 getIndex 會將 $news 的變數拋到某個 view 去:
// ArticleController.php
public function getIndex() {
// ORM...
// $articles = ....
return view('article.index', ['articles' => $articles]);
public function getIndex() {
// ORM...
// $articles = ....
return view('article.index', ['articles' => $articles]);
}
接下來,來做一點複雜的情況,假設在 article.view 加了一個搜尋用的 Form 表單,讓 user 可以搜尋文章的作者(Author),文章的標題(Title),文章的流水號(Id)。假設 user 送出了那個表單,應該發生什麼事情?
(1) 如果是用 GET method 送出表單,網址應該呈現 localhost/article?author=winwu&title=&id=&sort=id&order=desc
(2) view 得到的資料要是正確的
(3) 資料超過某個數量後,要顯示分頁
先來解決 (1),當搜尋的條件已經進到頁面時,ORM 就需要做一些改變,首先我會在 controller 決定我願意接受的搜尋條件參數: (簡單來說其實就是接 $_GET 參數啦! 只是我現在 focus 在 Laravel 上。)
$queryString = Request::only([ 'id', 'author', 'title', 'order', 'sort' ]);
接到 GET 參數後,接下來,需要把條件,帶到 ORM 的搜尋表達上,在這邊,我個人比較偏好 where 帶 $query 方式去做搜尋,因為比較彈性,也可以在 function 下一般的 if else 或是一些商業邏輯的判斷, 資料轉換:
// default desc
$order = (isset($queryString['order']) && ($queryString['order'] === 'asc')) ? 'asc' : 'desc';
// defaul sort
$sort = (isset($queryString['sort'])) ? $queryString['sort'] : 'id';
$articles = Article::where(function($query) use ($queryString) {
if (isset($queryString['id']) && $queryString['id'] !== "" ) {
$query->where('id', '=', $queryString['id'] );
}
if (isset($queryString['author']) && $queryString['author'] !== "" ) {
$query->where('author', 'LIKE', '%' . $queryString['author'] . '%');
}
// do other things
}) // 如果你需要 join 其他表,可以接在這裡:
/*
-> join('other_table', function ($join) {
$join->on('article.id', '=', 'other_table.reference')
})
*/;
// 處理order, sort 以及分頁數量設定 10 筆為一頁
$articles = $articles->orderBy($sort, $order)->paginate(10);
分頁筆數的部分,建議也可以拆出去用變數傳入。$order = (isset($queryString['order']) && ($queryString['order'] === 'asc')) ? 'asc' : 'desc';
// defaul sort
$sort = (isset($queryString['sort'])) ? $queryString['sort'] : 'id';
$articles = Article::where(function($query) use ($queryString) {
if (isset($queryString['id']) && $queryString['id'] !== "" ) {
$query->where('id', '=', $queryString['id'] );
}
if (isset($queryString['author']) && $queryString['author'] !== "" ) {
$query->where('author', 'LIKE', '%' . $queryString['author'] . '%');
}
// do other things
}) // 如果你需要 join 其他表,可以接在這裡:
/*
-> join('other_table', function ($join) {
$join->on('article.id', '=', 'other_table.reference')
})
*/;
// 處理order, sort 以及分頁數量設定 10 筆為一頁
$articles = $articles->orderBy($sort, $order)->paginate(10);
(1) 的部分,算是到一個段落了。
接下來,處理 (2)。
view 就只有兩件事,兩件事都可以簡單,也可以複雜。
(2-1) 顯示分頁
(2-2) 列表的表格 tr th 上,要有排序的功能可以點按。
(2-1) 相對單純,pagination 只要看官方文件即可,顯示方式也跟 orm 那邊的設定有關,如果你是用 ->simplePaginate(10) 那就只會呈現只有上一頁, 下一頁的分頁顯示,如果是 ->paginate(10),就會顯示比較完整的分頁。
顯示分頁,要在 article.view 加上 {!! $articles->render() !!} ,就會顯示出分頁了,這就是 (3) 。
不過這裡有個問題要注意,當我已經有搜尋某個條件之後,點到分頁的第二頁,原本的 query string 並不會帶到第二頁去,網址只會變成 localhost/article?page=2 而不會是 localhost/article?author=ssss&title=&id=&sort=id&order=desc&page=2。
這怎麼辦呢,有個好用的 method 叫做 appends(),可以把當下頁面上的搜尋條件參數,帶到分頁 link 的 url 上,你可以參考文件的 Appending To Pagination Links,或是像我一樣操作在 ORM 上。
我加上了 appends 到 articles 上。
$articles = $articles->orderBy($sort, $order)
->paginate(10)
->appends($querystringArray);
因為我把 appends 這個項目加在 ORM 上,所以我的 view 的 {!! $articles->render() !!} 完全不用調整。你可以挑一個做法來用。->paginate(10)
->appends($querystringArray);
(2-2) 的話,排序 icon 大部份都是採用 bootstrap 的 icon 或是 font-awesome,麻煩就在這個排序的按鈕跟分頁其實是有些類似的,他一樣要帶入當下的搜尋條件,除了 order 跟 sort 要改變。
這個部分我至今也沒有很好的解法,但一直寫類似的 code,有些麻煩,後來我把這塊的 html 拆出去寫成兩個 function (這兩個 function 可以寫在 article.index 的 view 的最上方,好一點的做法就是拆出去一個 class 去包裝他 )
function getArticleSortLinks($sortField) {
$sortLinks = route('article.index',
array(
'sort' => $sortField,
'order' => (isset($_GET['order']) && $_GET['order'] == 'desc')
? 'asc' : 'desc',
'id' => isset($_GET['id']) ? $_GET['id'] : '',
'author' => isset($_GET['author']) ? $_GET['author'] : '',
'title' => isset($_GET['title']) ? $_GET['title'] : ''
));
return $sortLinks;
}
$sortLinks = route('article.index',
array(
'sort' => $sortField,
'order' => (isset($_GET['order']) && $_GET['order'] == 'desc')
? 'asc' : 'desc',
'id' => isset($_GET['id']) ? $_GET['id'] : '',
'author' => isset($_GET['author']) ? $_GET['author'] : '',
'title' => isset($_GET['title']) ? $_GET['title'] : ''
));
return $sortLinks;
}
function getSortIcons($sortField) {
/*
這個寫法的 else 會有一些問題,但就簡單參考一下。
如果當下的排序是遞減,那個 icon 就要切換成遞增,反之。
然後預設我是擺 asc。
*/
if (isset($_GET['sort']) && ($_GET['sort'] == $sortField)) {
$sortLinkIcons = ''
. 'fa fa-sort-amount-'
. ( (isset($_GET['order']) && ($_GET['order'] == 'desc')) ? 'asc' : 'desc');
} else {
$sortLinkIcons = ''
. 'fa fa-sort-amount-asc';
}
return $sortLinkIcons;
}
/*
這個寫法的 else 會有一些問題,但就簡單參考一下。
如果當下的排序是遞減,那個 icon 就要切換成遞增,反之。
然後預設我是擺 asc。
*/
if (isset($_GET['sort']) && ($_GET['sort'] == $sortField)) {
$sortLinkIcons = ''
. 'fa fa-sort-amount-'
. ( (isset($_GET['order']) && ($_GET['order'] == 'desc')) ? 'asc' : 'desc');
} else {
$sortLinkIcons = ''
. 'fa fa-sort-amount-asc';
}
return $sortLinkIcons;
}
接著在排序的按鈕使用這個 function: (只舉某個欄位當作例子)
<th>文章標題
<a href="{{ getArticleSortLinks('title')}}">
<span class="{{ getSortIcons('title')}}"></span>
</a>
</th>
<a href="{{ getArticleSortLinks('title')}}">
<span class="{{ getSortIcons('title')}}"></span>
</a>
</th>
大概是這樣,有想到什麼我會再補充。
另外根據經驗,如果 ORM 需要 join 到三張表以上,會變得蠻慢的... QQ。
下週還會跟公司的架構師做一些 code 的調整,有什麼我覺得一定會修正的部分,我會再補上來 :P