@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
at recaptime-dev/main 243 lines 6.3 kB view raw
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}