00001 <?php
00002
00073 class TKPagedView extends TKContainer {
00074 private $callback = null;
00075 private $name = null;
00076 private $useAjax = false;
00077 private $saveState = false;
00078 private $tableId = '';
00079
00090 public function __construct($name, $callback, $useAjax = false, $saveState = false) {
00091 parent::__construct();
00092
00093 $this->callback = $callback;
00094
00095 $this->name = $name;
00096 $this->useAjax = $useAjax;
00097 $this->saveState = $saveState;
00098
00099 $this->setId('pagedView_' . $this->name);
00100
00101 $this->addProperty('offset', self::PROP_NUMERIC);
00102 $this->addProperty('perPage', self::PROP_NUMERIC);
00103 $this->addProperty('sortBy', self::PROP_STRING);
00104 $this->addProperty('sortOrder', self::PROP_STRING);
00105 $this->addProperty('maxPerPage', self::PROP_NUMERIC);
00106 $this->addProperty('perPageInterval', self::PROP_NUMERIC);
00107 $this->addProperty('sortFields', self::PROP_ARRAY);
00108 $this->addProperty('headers', self::PROP_ARRAY);
00109 $this->addProperty('title', self::PROP_STRING);
00110 $this->addProperty('tableObjectClass', self::PROP_STRING);
00111 $this->addProperty('scrollable', self::PROP_BOOLEAN);
00112 $this->addProperty('animate', self::PROP_BOOLEAN);
00113 $this->addProperty('bottomNav', self::PROP_BOOLEAN);
00114 $this->addProperty('advancedNav', self::PROP_BOOLEAN);
00115 $this->addProperty('scrollHeight', self::PROP_STRING);
00116 $this->addProperty('scrollWidth', self::PROP_STRING);
00117
00118 $this->addProperty('header_class', self::PROP_CLASS);
00119 $this->addProperty('count_class', self::PROP_CLASS);
00120 $this->addProperty('navigation_class', self::PROP_CLASS);
00121 $this->addProperty('nav_button_class', self::PROP_CLASS);
00122 $this->addProperty('nav_button_disabled_class', self::PROP_CLASS);
00123 $this->addProperty('nav_select_class', self::PROP_CLASS);
00124 $this->addProperty('nav_label_class', self::PROP_CLASS);
00125 $this->addProperty('table_class', self::PROP_CLASS);
00126 $this->addProperty('no_results_class', self::PROP_CLASS);
00127
00128 $this->offset = isset($_REQUEST[$name . '_offset']) && ctype_digit((string) $_REQUEST[$name . '_offset'])
00129 && $_REQUEST[$name . '_offset'] > 0 ? $_REQUEST[$name . '_offset'] : 0;
00130
00131 $this->maxPerPage = 400;
00132 $this->perPageInterval = 25;
00133 $this->perPage = 25;
00134 $this->tableObjectClass = 'TKTable';
00135 $this->scrollHeight = '400px';
00136 $this->scrollWidth = '100%';
00137 $this->sortOrder = 'ASC';
00138
00139 if($useAjax && $this->saveState && isset($_SESSION['PV_CONFIG_' . $this->name])) {
00140 $session = unserialize($_SESSION['PV_CONFIG_' . $this->name]);
00141 $this->offset = $session['offset'];
00142 $this->perPage = $session['perPage'];
00143 $this->sortBy = $session['sortBy'];
00144 $this->sortOrder = $session['sortOrder'];
00145 }
00146 }
00147
00158 protected function build() {
00159 $cb = $this->callback;
00160 if(!is_callable($cb)) {
00161 $this->setError('Populate callback is not a valid callback!');
00162 return false;
00163 }
00164
00165 if($this->sortBy == '' && count($this->sortFields) > 0)
00166 $this->sortBy = $this->sortFields[0];
00167 if($this->sortOrder !== 'ASC' && $this->sortOrder !== 'DESC')
00168 $this->sortOrder = 'ASC';
00169
00170 list($data, $total) = call_user_func($cb, $this->name, $this->offset, $this->perPage, $this->sortBy, $this->sortOrder);
00171 if($data === false) {
00172 $this->setError('Failed to populate view!');
00173 return false;
00174 }
00175
00176 $header = $this->buildHeader();
00177 $this->add($header);
00178 if(count($data) == 0) {
00179 $nrClass = Toolkit::classToString($this->no_results_class);
00180 $this->addText("<div$nrClass>No results</div>");
00181 return true;
00182 }
00183
00184 if($this->offset >= $total)
00185 $this->offset = $total - ($total % $this->perPage);
00186 $start = $this->offset;
00187 $end = ($this->offset + $this->perPage > $total ? $total : $this->offset + $this->perPage);
00188
00189 $posLabel = $this->buildPositionLabel($start, $end, $total);
00190 $table = $this->buildTable($data);
00191 $upperBBox = $this->buildButtonBox($start, $end, $total, 1);
00192 if($this->bottomNav)
00193 $lowerBBox = $this->buildButtonBox($start, $end, $total, 2);
00194
00195 $this->add($posLabel);
00196 $this->add($upperBBox);
00197 $this->add($table);
00198 if($this->bottomNav)
00199 $this->add($lowerBBox);
00200
00201 return true;
00202 }
00203
00204 protected function buildHeader() {
00205 if(count($this->header_class) > 0)
00206 $class = ' class="' . implode(' ', $this->header_class) . '"';
00207 else
00208 $class = '';
00209 $header = new TKLabel('<h3' . $class . '>' . $this->title . '</h3>');
00210 $header->class += $this->header_class;
00211 return $header;
00212 }
00213
00214 protected function buildPositionLabel($start, $end, $total) {
00215 $dStart = $start + 1;
00216 $dEnd = $end;
00217 if($dEnd > $total)
00218 $dEnd = $total;
00219 $label = new TKLabel("$dStart to $dEnd of $total");
00220 $label->class += $this->count_class;
00221 return $label;
00222 }
00223
00235 protected function buildButtonBox($start, $end, $total, $number) {
00236 $hbox = new TKHBox();
00237 $hbox->append('class', $this->navigation_class);
00238
00239 $this->buildPageSelector($start, $end, $total, $number, $hbox);
00240 if($this->advancedNav) {
00241 $this->buildResultsPerPageSelector($start, $end, $total, $number, $hbox);
00242 if(count($this->sortFields) > 0)
00243 $this->buildSortSelector($start, $end, $total, $number, $hbox);
00244 }
00245
00246 return $hbox;
00247 }
00248
00249 protected function buildPageSelector($start, $end, $total, $number, $hbox) {
00250 $totalPages = ceil($total / $this->perPage);
00251 $curPage = floor($start / $this->perPage);
00252
00253 $navButtonC = $this->getTranslatedClass('nav_button');
00254 $navMixedC = $this->getTranslatedClass('nav_button_disabled');
00255
00256 if($this->offset > 0) {
00257 if($this->useAjax) {
00258 $hbox->addLabel('<a href="javascript:void(0)"' . $navButtonC
00259 . '><<</a>')->onclick = $this->buildAjax(0);
00260 $hbox->addLabel('<a href="javascript:void(0)"' . $navButtonC
00261 . '><</a>')->onclick = $this->buildAjax($start - $this->perPage);
00262 }
00263 else {
00264 $hbox->addText('<a href="' . $this->buildURL(0) . '"' . $navButtonC . '><<</a>');
00265 $hbox->addText('<a href="' . $this->buildURL($start - $this->perPage) . '"' . $navButtonC . '><</a>');
00266 }
00267 }
00268 else {
00269 $hbox->addText('<span' . $navMixedC . '><<</span>');
00270 $hbox->addText('<span' . $navMixedC . '><</span>');
00271 }
00272
00273 $min = $curPage - 2 < 0 ? 0 : $curPage - 2;
00274 $max = $curPage + 2 >= $totalPages ? $totalPages - 1 : $curPage + 2;
00275 if($min > 0)
00276 $hbox->addText('<span' . $navMixedC . '>...</span>');
00277 for($i = $min; $i <= $max; $i++) {
00278 if($i == $curPage)
00279 $hbox->addText('<span' . $navMixedC . '>' . ($i + 1) . '</span>');
00280 else {
00281 if($this->useAjax)
00282 $hbox->addLabel('<a href="javascript:void(0)"' . $navButtonC
00283 . '>' . ($i + 1) . '</a>')->onclick = $this->buildAjax($i * $this->perPage);
00284 else
00285 $hbox->addText("<a href=\"" . $this->buildURL($i * $this->perPage) . '"'
00286 . $navButtonC . '>' . ($i + 1) . '</a>');
00287 }
00288 }
00289 if($max < $totalPages - 1)
00290 $hbox->addText('<span' . $navMixedC . '>...</span>');
00291
00292 if($end < $total) {
00293 $endPage = intval($total / $this->perPage) * $this->perPage;
00294 if($endPage == $total)
00295 $endPage -= $this->perPage;
00296 if($this->useAjax) {
00297 $hbox->addLabel('<a href="javascript:void(0)"' . $navButtonC
00298 . '>></a>')->onclick = $this->buildAjax($start + $this->perPage);
00299 $hbox->addLabel('<a href="javascript:void(0)"' . $navButtonC
00300 . '>>></a>')->onclick = $this->buildAjax($endPage);
00301 }
00302 else {
00303 $hbox->addText('<a href="' . $this->buildURL($start + $this->perPage) . '"' . $navButtonC
00304 . '>></a>');
00305 $hbox->addText('<a href="' . $this->buildURL($endPage) . '"' . $navButtonC
00306 . '>>></a>');
00307 }
00308 }
00309 else {
00310 $hbox->addText('<span' . $navMixedC . '>></span>');
00311 $hbox->addText('<span' . $navMixedC . '>>></span>');
00312 }
00313 }
00314
00315 protected function buildResultsPerPageSelector($start, $end, $total, $number, $hbox) {
00316 $select = new TKSelect('ResultsPerPage');
00317
00318 $maxPerPage = min($this->maxPerPage, ($total % $this->perPageInterval == 0 ? $total : $total + $this->perPageInterval));
00319 for($i = $this->perPageInterval; $i <= $maxPerPage; $i += $this->perPageInterval) {
00320 $select->addOption($i, $i);
00321 }
00322 $select->selected = $this->perPage;
00323
00324 $select->onchange = "Toolkit.PagedView['{$this->getId()}'].value = this.options[this.selectedIndex].value;"
00325 . $this->buildAjax("Toolkit.PagedView['{$this->getId()}'].value * Math.floor(" . $this->offset
00326 . " / Toolkit.PagedView['{$this->getId()}'].value)", "Toolkit.PagedView['{$this->getId()}'].value");
00327 $form = <<<END_FORM
00328 {$select->render()}
00329 <label for="{$select->getId()}"> results per page</label>
00330 END_FORM;
00331 $hbox->addText($form);
00332 }
00333
00334 protected function buildSortSelector($start, $end, $total, $number, $hbox) {
00335 $sortSelect = new TKSelect('SortBy');
00336 $sortSelect->class += $this->nav_select_class;
00337
00338 foreach($this->sortFields as $i => $field)
00339 $sortSelect->addOption($field, $field);
00340 $sortSelect->selected = $this->sortBy;
00341
00342 $sortSelect->onchange = "Toolkit.doPagedView('{$this->getId()}', '{$this->name}', '{$this->offset}', '{$this->offset}',"
00343 . "'{$this->perPage}', this.options[this.selectedIndex].value, '{$this->sortOrder}')";
00344
00345 $orderSelect = new TKSelect('Ordering');
00346 $orderSelect->class += $this->nav_select_class;
00347 $orderSelect->addOption('ASC', 'Ascending');
00348 $orderSelect->addOption('DESC', 'Descending');
00349 $orderSelect->selected = $this->sortOrder;
00350
00351 $orderSelect->onchange = "Toolkit.doPagedView('{$this->getId()}', '{$this->name}', '{$this->offset}', '{$this->offset}',"
00352 . "'{$this->perPage}', '{$this->sortBy}', this.options[this.selectedIndex].value)";
00353
00354 $labelClass = $this->getTranslatedClass('nav_label');
00355
00356 $form = <<<END_FORM
00357 <label $labelClass for="{$sortSelect->getId()}">, sorted by</label>
00358 {$sortSelect->render()}
00359 <label $labelClass for="{$orderSelect->getId()}">in</label>
00360 {$orderSelect->render()}
00361 <label $labelClass for="{$orderSelect->getId()}">order</label>
00362 END_FORM;
00363 $hbox->addText($form);
00364 }
00365
00375 private function buildURL($offset) {
00376 $info = parse_url($_SERVER['REQUEST_URI']);
00377 if(isset($info['query']))
00378 parse_str($info['query'], $vars);
00379 else
00380 $vars = array();
00381 $pos = strpos($_SERVER['REQUEST_URI'], '?');
00382 if($pos === false)
00383 $uriBase = $_SERVER['REQUEST_URI'];
00384 else
00385 $uriBase = substr($_SERVER['REQUEST_URI'], 0, $pos);
00386
00387 if($offset > 0)
00388 $vars[$this->name . '_offset'] = $offset;
00389 else
00390 unset($vars[$this->name . '_offset']);
00391
00392 if($this->sortBy != '')
00393 $vars[$this->name . '_sortBy'] = $this->sortBy;
00394 else
00395 unset($vars[$this->name . '_sortBy']);
00396
00397 $ret = http_build_query($vars);
00398 if($ret != '')
00399 $ret = $uriBase . '?' . $ret;
00400 else
00401 $ret = $uriBase;
00402
00403 return $ret;
00404 }
00405
00417 private function buildAjax($offset, $perPage = null) {
00418 $id = $this->getId();
00419
00420 if(is_null($perPage))
00421 $perPage = $this->perPage;
00422
00423 return "Toolkit.doPagedView('$id', '{$this->name}', '{$this->offset}', $offset, $perPage, '{$this->sortBy}')";
00424 }
00425
00436 protected function buildTable($data) {
00437 if(count($data) == 0)
00438 return new TKLabel("<p><i>No results</i></p>");
00439
00440 $tableObjectClass = $this->tableObjectClass;
00441
00442
00443
00444 $table = new $tableObjectClass(false);
00445
00446 $table->class = 'tkPagedListingTable';
00447 $table->append('class', $this->table_class);
00448 $table->style['margin-bottom'] = 0;
00449
00450 foreach($this->headers as $header) {
00451 if(is_array($header)) {
00452 $hName = key($header);
00453 $hColspan = current($header);
00454 $table->addTextTo('header', $hName, array('colspan' => $hColspan));
00455 }
00456 else
00457 $table->addTextTo('header', $header);
00458 }
00459
00460 foreach($data as $row) {
00461 $table->addRow();
00462 foreach($row as $cell) {
00463 if(is_string($cell) || ctype_digit((string) $cell))
00464 $table->addText($cell);
00465 else if(is_object($cell) && $cell instanceof TKComponent)
00466 $table->add($cell);
00467 else if(is_array($cell)) {
00468 $params = array();
00469 if(isset($cell['colspan']))
00470 $params['colspan'] = $cell['colspan'];
00471 if(isset($cell['style']))
00472 $params['style'] = $cell['style'];
00473 if(isset($cell['class']))
00474 $params['class'] = $cell['class'];
00475 if(is_string($cell[0]) || ctype_digit((string) $cell[0]))
00476 $table->addText($cell[0], $params);
00477 else if(is_object($cell[0]) && $cell instanceof TKComponent)
00478 $table->add($cell[0], $params);
00479 else
00480 $table->addText(' ', $params);
00481 }
00482 else
00483 $table->addText(' ');
00484 }
00485 }
00486
00487 if($this->scrollable) {
00488 $sv = new TKScrollPane();
00489 $sv->style['height'] = $this->scrollHeight;
00490 $sv->style['width'] = $this->scrollWidth;
00491 $sv->add($table);
00492 $ret = $sv;
00493 }
00494 else
00495 $ret = $table;
00496 $ret->setId($this->name . '_table_' . $this->offset);
00497 if($this->animate && isset($_REQUEST[$this->name . '_isAjax']))
00498 $ret->style['opacity'] = '0';
00499 else
00500 $ret->style['opacity'] = '100';
00501 return $ret;
00502 }
00503
00517 protected function renderContainer($class, $style, $events, $id) {
00518 $this->build();
00519
00520 $html = "<div $id$class[0]$style[0]$events>";
00521
00522 foreach($this->getChildrenBySlot('main') as $cid => $comp) {
00523 if(is_object($comp))
00524 $html .= $comp->render();
00525 else
00526 $html .= $comp;
00527 }
00528
00529 $html .= '</div>';
00530
00531 if($this->useAjax && !Toolkit::issetJS('tk2_doPagedView')) {
00532 $js = <<<END_JAVASCRIPT
00533 Toolkit.PagedView = {};
00534 Toolkit.doPagedView = function(id, name, oldOffset, offset, perPage, sortBy, sortOrder) {
00535 var params = {};
00536 params[name + '_isAjax'] = true;
00537 params[name + '_offset'] = offset;
00538 params[name + '_perPage'] = perPage;
00539 params[name + '_sortBy'] = sortBy;
00540 params[name + '_sortOrder'] = sortOrder;
00541 new Ajax.Request('$_SERVER[REQUEST_URI]', {
00542 method: 'post',
00543 parameters: params,
00544 onSuccess: function(transport) {
00545 END_JAVASCRIPT;
00546 if($this->animate) {
00547 $js .= <<<END_JAVASCRIPT
00548
00549 new Effect.Opacity(name + '_table_' + oldOffset, { from: 1.0, to: 0, duration: 0.2, afterFinish: function() {
00550 $(id).replace(transport.responseText);
00551 new Effect.Opacity(name + '_table_' + offset, { from: 0, to: 1.0, duration: 0.3, queue: 'end' });
00552 }});
00553 END_JAVASCRIPT;
00554 }
00555 else
00556 $js .= "\t\t\t$(id).update(transport.responseText);\n";
00557 $js .= <<<END_JAVASCRIPT
00558
00559 },
00560 onFailure: function() {
00561 alert('Something went wrong...');
00562 }
00563 });
00564 }
00565 END_JAVASCRIPT;
00566 Toolkit::globalAddExtraJS('tk2_doPagedView', $js);
00567 Toolkit::globalAddExtraJS('tk2_doPagedView' . $this->getId(), "Toolkit.PagedView['{$this->getId()}'] = {}");
00568 }
00569 else
00570 Toolkit::globalAddExtraJS('tk2_doPagedView' . $this->getId(), "Toolkit.PagedView['{$this->getId()}'] = {}");
00571
00572 return $html;
00573 }
00574
00582 public function handleAjax() {
00583 if(!$this->useAjax || !isset($_REQUEST[$this->name . '_isAjax']))
00584 return;
00585 STimer::enable(false);
00586 $this->offset = isset($_REQUEST[$this->name . '_offset']) ? $_REQUEST[$this->name . '_offset'] : 0;
00587 if(isset($_REQUEST[$this->name . '_perPage'])) {
00588 $perPage = $_REQUEST[$this->name . '_perPage'];
00589 if(ctype_digit($perPage) && $perPage % $this->perPageInterval == 0 && $perPage <= $this->maxPerPage)
00590 $this->perPage = $perPage;
00591 }
00592 if(isset($_REQUEST[$this->name . '_sortBy'])) {
00593 $sortBy = $_REQUEST[$this->name . '_sortBy'];
00594 if($sortBy != '' && in_array($sortBy, $this->sortFields))
00595 $this->sortBy = $sortBy;
00596 }
00597 if(isset($_REQUEST[$this->name . '_sortOrder'])) {
00598 $sortOrder = $_REQUEST[$this->name . '_sortOrder'];
00599 if($sortOrder === 'ASC' || $sortOrder === 'DESC')
00600 $this->sortOrder = $sortOrder;
00601 }
00602 $html = $this->render();
00603 if($this->saveState) {
00604 $_SESSION['PV_CONFIG_' . $this->name] = serialize(array('offset' => $this->offset, 'perPage' => $this->perPage,
00605 'sortBy' => $this->sortBy, 'sortOrder' => $this->sortOrder));
00606 }
00607 print $html;
00608 exit;
00609 }
00610 }
00611
00612 ?>