书籍列表页面

在这个页面,我们会分页显示书籍列表页面。同时我们也会看到,SF中一个模板是可以被多个控制器复用的。当然,我们还要讨论如何编写分页。

分页模块

首先我们看看分页模块的编写。

src目录下创建一个lib目录,并创建一个Paginator.php

  1. namespace lib;
  2. class Paginator
  3. {
  4. private $totalPages;
  5. private $page;
  6. private $rpp;
  7. public function __construct($page, $totalcount, $rpp)
  8. {
  9. $this->rpp=$rpp;
  10. $this->page=$page;
  11. $this->totalPages=$this->setTotalPages($totalcount, $rpp);
  12. }
  13. /*
  14. * var recCount: the total count of records
  15. * var $rpp: the record per page
  16. */
  17. private function setTotalPages($totalcount, $rpp)
  18. {
  19. if ($rpp == 0)
  20. {
  21. $rpp = 20; //This is forced in this. Need to get parameter from configuration but seems not necessary
  22. }
  23. $this->totalPages=ceil($totalcount / $rpp);
  24. return $this->totalPages;
  25. }
  26. public function getTotalPages()
  27. {
  28. return $this->totalPages;
  29. }
  30. public function getPagesList()
  31. {
  32. $pageCount = 5;
  33. if ($this->totalPages <= $pageCount) //Less than total 5 pages
  34. {
  35. return [1, 2, 3, 4, 5];
  36. }
  37. if($this->page <=3)
  38. {
  39. return [1,2,3,4,5];
  40. }
  41. $i = $pageCount;
  42. $r=array();
  43. $half = floor($pageCount / 2);
  44. if ($this->page + $half > $this->totalPages) // Close to end
  45. {
  46. while ($i >= 1)
  47. {
  48. $r[] = $this->totalPages - $i + 1;
  49. $i--;
  50. }
  51. return $r;
  52. } else
  53. {
  54. while ($i >= 1)
  55. {
  56. $r[] = $this->page - $i + $half + 1;
  57. $i--;
  58. }
  59. return $r;
  60. }
  61. }
  62. }

这个模块只负责一件事情:根据记录总数和每页的记录数算出总页面,并根据当前页数返回一个(合理的)包含前后各2个页数及当前页数(共5个)的数组,使得调用端可以显示去往不同页面的链接。这个类的编写并不复杂,这里不再做进一步的解释。

书籍列表的路由定义

书籍列表的路由定义比较长,这是因为在设计这个路由(和对应的动作)时,我们考虑要将该路由(和对应的动作)复用。它不仅只是简单地按照登录顺序的逆序分页显示若干书籍,而且还能按照搜索方式的不同只显示符合搜索条件的若干书籍1

该路由定义如下:

  1. book_list:
  2. path: /books/list/{type}/{key}/{page}
  3. defaults:
  4. page: 1
  5. type: title
  6. key: all
  7. _controller: AppBundle:Book:list

这个路由定义的路径可以解读为:按照某个类型(type)下的关键字(key)搜索并返回搜索结果中的某一页(page)。

书籍列表动作的编写

由于我们采用API的方式获得远程数据,所以控制器中的动作编写相对简单:

  1. public function listAction($page, $key, $type)
  2. {
  3. $uri="http://api/books/list/$type/$key/$page";
  4. $out= json_decode(file_get_contents($uri))->out;
  5. $res=$out->books;
  6. $totalcount=$out->count->bc;
  7. $rpp=$this->container->getParameter('books_per_page');
  8. $paginator = new \lib\Paginator($page, $totalcount, $rpp);
  9. $pagelist = $paginator->getPagesList();
  10. return $this->render("AppBundle:book:list.html.twig", array('res' => $res, 'paginator' => $pagelist, 'cur' => $page, 'total' => $paginator->getTotalPages(), 'key' => $key, 'type' => $type));
  11. }

简单说来,我们通过API调用获得适当的数据(符合搜索条件的书籍和书籍总数),从SF全局配置文件中获得books_per_page这个参数并调用上文提到的Paginator类构造一个分页列表。最后将相应的参数(共6个)传递到模板中显示。

注意: books_per_page参数应该在/app/config/parameters.yml中得到定义。方法是在该文件中加入一行:

  1. books_per_page: 20

书籍列表模板

书籍列表模板比较长,这里不再列出。我们只是重点分析一下分页导航部分的代码。

  1. <section id="pagination" class="col-md-12">
  2. <a href="{{path('book_list', {'page':1, 'key':key, 'type':type})}}" title="首页"><i class="glyphicon glyphicon-fast-backward"></i></a>
  3. {% if cur==1 %}
  4. <a class="disabled" title="上一页"><i class="glyphicon glyphicon-backward"></i></a>
  5. {% else %}
  6. <a href="{{path('book_list', {'page':cur-1, 'key':key, 'type':type})}}" title="上一页"><i class="glyphicon glyphicon-backward"></i></a>
  7. {% endif %}
  8. {% if cur==total %}
  9. <a class="disabled" title="下一页"><i class="glyphicons forward"></i></a>
  10. {% else %}
  11. <a href="{{path('book_list', {'page':cur+1, 'key': key, 'type':type})}}" title="下一页"><i class="glyphicon glyphicon-forward"></i></a>
  12. {% endif %}
  13. <a href="{{path('book_list', {'page':total, 'key':key, 'type':type})}}" title="末页"><i class="glyphicon glyphicon-fast-forward"></i></a>
  14. </section>

虽然说从Paginator获得的是一个页面导航列表,但是我们选择用“前后页”(加上首页、末页)的方式显示导航按钮。也因此,我们分离了分页本身、导航、显示这三部分。这样做能提供最大程度的灵活性。

效果

至此页面效果如下:

5.10. 书籍列表页面  - 图1

当然,这个页面还有一些功能没有完成。比如搜索以及直接跳转页面。搜索会在后续章节讲述。直接页面跳转比较简单,请自行完成。

1. 目前只支持按照书籍标题起始字符搜索和单一TAG搜索。