@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class PHUIPagerView extends AphrontView {
4
5 private $offset;
6 private $pageSize = 100;
7
8 private $count;
9 private $hasMorePages;
10
11 private $uri;
12 private $pagingParameter;
13 private $surroundingPages = 2;
14 private $enableKeyboardShortcuts;
15
16 public function setPageSize($page_size) {
17 $this->pageSize = max(1, $page_size);
18 return $this;
19 }
20
21 public function setOffset($offset) {
22 $this->offset = max(0, $offset);
23 return $this;
24 }
25
26 public function getOffset() {
27 return $this->offset;
28 }
29
30 public function getPageSize() {
31 return $this->pageSize;
32 }
33
34 public function setCount($count) {
35 $this->count = $count;
36 return $this;
37 }
38
39 public function setHasMorePages($has_more) {
40 $this->hasMorePages = $has_more;
41 return $this;
42 }
43
44 public function setURI(PhutilURI $uri, $paging_parameter) {
45 $this->uri = $uri;
46 $this->pagingParameter = $paging_parameter;
47 return $this;
48 }
49
50 public function readFromRequest(AphrontRequest $request) {
51 $this->uri = $request->getRequestURI();
52 $this->pagingParameter = 'offset';
53 $this->offset = $request->getInt($this->pagingParameter);
54 return $this;
55 }
56
57 public function willShowPagingControls() {
58 return $this->hasMorePages || $this->getOffset();
59 }
60
61 public function getHasMorePages() {
62 return $this->hasMorePages;
63 }
64
65 public function setSurroundingPages($pages) {
66 $this->surroundingPages = max(0, $pages);
67 return $this;
68 }
69
70 private function computeCount() {
71 if ($this->count !== null) {
72 return $this->count;
73 }
74 return $this->getOffset()
75 + $this->getPageSize()
76 + ($this->hasMorePages ? 1 : 0);
77 }
78
79 /**
80 * A common paging strategy is to select one extra record and use that to
81 * indicate that there's an additional page (this doesn't give you a
82 * complete page count but is often faster than counting the total number
83 * of items). This method will take a result array, slice it down to the
84 * page size if necessary, and call setHasMorePages() if there are more than
85 * one page of results.
86 *
87 * $results = queryfx_all(
88 * $conn,
89 * 'SELECT ... LIMIT %d, %d',
90 * $pager->getOffset(),
91 * $pager->getPageSize() + 1);
92 * $results = $pager->sliceResults($results);
93 *
94 * @param list $results Result array.
95 * @return list One page of results.
96 */
97 public function sliceResults(array $results) {
98 if (count($results) > $this->getPageSize()) {
99 $results = array_slice($results, 0, $this->getPageSize(), true);
100 $this->setHasMorePages(true);
101 }
102 return $results;
103 }
104
105 public function setEnableKeyboardShortcuts($enable) {
106 $this->enableKeyboardShortcuts = $enable;
107 return $this;
108 }
109
110 public function render() {
111 if (!$this->uri) {
112 throw new PhutilInvalidStateException('setURI');
113 }
114
115 require_celerity_resource('phui-pager-css');
116
117 $page = (int)floor($this->getOffset() / $this->getPageSize());
118 $last = ((int)ceil($this->computeCount() / $this->getPageSize())) - 1;
119 $near = $this->surroundingPages;
120
121 $min = $page - $near;
122 $max = $page + $near;
123
124 // Limit the window size to no larger than the number of available pages.
125 if ($max - $min > $last) {
126 $max = $min + $last;
127 if ($max == $min) {
128 return phutil_tag('div', array('class' => 'phui-pager-view'), '');
129 }
130 }
131
132 // Slide the window so it is entirely over displayable pages.
133 if ($min < 0) {
134 $max += 0 - $min;
135 $min += 0 - $min;
136 }
137
138 if ($max > $last) {
139 $min -= $max - $last;
140 $max -= $max - $last;
141 }
142
143
144 // Build up a list of <index, label, css-class> tuples which describe the
145 // links we'll display, then render them all at once.
146
147 $links = array();
148
149 $prev_index = null;
150 $next_index = null;
151
152 if ($min > 0) {
153 $links[] = array(0, pht('First'), null);
154 }
155
156 if ($page > 0) {
157 $links[] = array($page - 1, pht('Prev'), null);
158 $prev_index = $page - 1;
159 }
160
161 for ($ii = $min; $ii <= $max; $ii++) {
162 $links[] = array($ii, $ii + 1, ($ii == $page) ? 'current' : null);
163 }
164
165 if ($page < $last && $last > 0) {
166 $links[] = array($page + 1, pht('Next'), null);
167 $next_index = $page + 1;
168 }
169
170 if ($max < ($last - 1)) {
171 $links[] = array($last, pht('Last'), null);
172 }
173
174 $base_uri = $this->uri;
175 $parameter = $this->pagingParameter;
176
177 if ($this->enableKeyboardShortcuts) {
178 $pager_links = array();
179 $pager_index = array(
180 'prev' => $prev_index,
181 'next' => $next_index,
182 );
183 foreach ($pager_index as $key => $index) {
184 if ($index !== null) {
185 $display_index = $this->getDisplayIndex($index);
186
187 $uri = id(clone $base_uri);
188 if ($display_index === null) {
189 $uri->removeQueryParam($parameter);
190 } else {
191 $uri->replaceQueryParam($parameter, $display_index);
192 }
193
194 $pager_links[$key] = phutil_string_cast($uri);
195 }
196 }
197 Javelin::initBehavior('phabricator-keyboard-pager', $pager_links);
198 }
199
200 // Convert tuples into rendered nodes.
201 $rendered_links = array();
202 foreach ($links as $link) {
203 list($index, $label, $class) = $link;
204 $display_index = $this->getDisplayIndex($index);
205
206 $uri = id(clone $base_uri);
207 if ($display_index === null) {
208 $uri->removeQueryParam($parameter);
209 } else {
210 $uri->replaceQueryParam($parameter, $display_index);
211 }
212
213 $rendered_links[] = id(new PHUIButtonView())
214 ->setTag('a')
215 ->setHref($uri)
216 ->setColor(PHUIButtonView::GREY)
217 ->addClass('mml')
218 ->addClass($class)
219 ->setText($label);
220 }
221
222 return phutil_tag(
223 'div',
224 array(
225 'class' => 'phui-pager-view',
226 ),
227 $rendered_links);
228 }
229
230 private function getDisplayIndex($page_index) {
231 $page_size = $this->getPageSize();
232 // Use a 1-based sequence for display so that the number in the URI is
233 // the same as the page number you're on.
234 if ($page_index == 0) {
235 // No need for the first page to say page=1.
236 $display_index = null;
237 } else {
238 $display_index = $page_index * $page_size;
239 }
240 return $display_index;
241 }
242
243}