460 lines
12 KiB
PHP
460 lines
12 KiB
PHP
<?php defined('SYSPATH') or die('No direct access allowed.');
|
|
|
|
/**
|
|
* This class is for rendering a table of data.
|
|
*
|
|
* @package lnApp
|
|
* @category Helpers
|
|
* @author Deon George
|
|
* @copyright (c) 2009-2013 Deon George
|
|
* @license http://dev.leenooks.net/license.html
|
|
*/
|
|
abstract class lnApp_Table {
|
|
private $data = NULL;
|
|
private $columns = array(); // Our columns that we need to display
|
|
private $filters = array(); // Our datafomrating that we need to use
|
|
private $filters_added = FALSE;
|
|
private $jssort = FALSE; // Use the JS pager and sorter
|
|
private $other = ''; // If we are only listing x items, this will be the other summary line
|
|
private $other_col = NULL; // If we are only listing x items, this will be the other summary line
|
|
private $po = NULL; // If we are paging the results, this is the page we are displaying
|
|
private $page = FALSE; // If we should page the results
|
|
private $page_rows = 0; // Number of rows per page
|
|
private $prepend = array(); // Our data formating that we need to use, prepended to the value
|
|
private $postproc = array(); // Our data formating that we need to use after the value is determined
|
|
private $select = array(); // Our select form details
|
|
|
|
public function __toString() {
|
|
try {
|
|
return (string)$this->render();
|
|
|
|
} catch (Exception $e) {
|
|
return $e->getMessage();
|
|
}
|
|
}
|
|
|
|
public function columns($cols) {
|
|
$this->columns = $cols;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function data($data) {
|
|
$this->data = $data;
|
|
|
|
return $this;
|
|
}
|
|
|
|
private function evaluate($d,$key) {
|
|
if (count($this->filters) AND ! $this->filters_added) {
|
|
$d->display_filters($this->filters);
|
|
// So we dont keep extending our display_filters
|
|
$this->filters_added = TRUE;
|
|
}
|
|
|
|
if (is_array($d) AND isset($d[$key]))
|
|
$x = $d[$key];
|
|
|
|
// If the key is a method, we need to eval it
|
|
elseif (preg_match('/\(/',$key) OR preg_match('/-\>/',$key))
|
|
eval("\$x = \$d->$key;");
|
|
|
|
elseif (preg_match('/^__VALUE__$/',$key))
|
|
$x = $d;
|
|
|
|
else
|
|
$x = $d->display($key);
|
|
|
|
if (isset($this->prepend[$key]) AND $x) {
|
|
foreach ($this->prepend[$key] as $act => $data) {
|
|
switch ($act) {
|
|
case 'checkbox': $x = sprintf('<label class="checkbox">%s %s</label>',Form::checkbox($data,$x,FALSE,array('nocg'=>TRUE)),$x); // @todo Check those not yet checked
|
|
break;
|
|
|
|
case 'input':
|
|
if (preg_match('/__VALUE__/',$data['key']))
|
|
$data['key'] = preg_replace('/__VALUE__/',$x,$data['key']);
|
|
|
|
$x = Form::input($data['key'],isset($data['values'][$x]) ? $data['values'][$x] : '',array('placeholder'=>$x,'label'=>$x));
|
|
break;
|
|
|
|
case 'url': $x = HTML::anchor($data.$x,$x);
|
|
break;
|
|
|
|
case 'url_resolve':
|
|
$filtermatchall = array();
|
|
preg_match_all('/%(\w+)(\|.+)?(\/[lUC])?%/U',$data,$filtermatchall);
|
|
|
|
foreach ($filtermatchall[1] as $id=>$key)
|
|
$data = str_replace($filtermatchall[0][$id],$d->display($key),$data);
|
|
|
|
$x = HTML::anchor($data,$x);
|
|
break;
|
|
|
|
default:
|
|
throw HTTP_Exception::factory(501,'Unknown action :action',array(':action'=>$act));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($this->postproc[$key]) and $x) {
|
|
foreach ($this->postproc[$key] as $act => $data) {
|
|
switch ($act) {
|
|
case 'trim':
|
|
if (strlen($x) > $data) {
|
|
$x = sprintf('<abbr title="%s">%s</abbr>',$x,substr($x,0,$data-3).str_repeat('.',3));
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
throw HTTP_Exception::factory(501,'Unknown action :action',array(':action'=>$act));
|
|
}
|
|
}
|
|
}
|
|
|
|
return $x;
|
|
}
|
|
|
|
public static function factory() {
|
|
return new Table;
|
|
}
|
|
|
|
/**
|
|
* Apply additional display filters to the results
|
|
*/
|
|
public function filters($cols) {
|
|
$this->filters = $cols;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function jssort($table) {
|
|
$this->jssort = $table;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function page_items($num) {
|
|
$this->page = TRUE;
|
|
$this->page_rows = $num;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function prepend($cols) {
|
|
$this->prepend = $cols;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function postproc($cols) {
|
|
$this->postproc = $cols;
|
|
|
|
return $this;
|
|
}
|
|
|
|
private function process() {
|
|
$result = array();
|
|
|
|
$row = 0;
|
|
foreach ($this->data as $o) {
|
|
$row++;
|
|
$c = 0;
|
|
// So our filters are added for this record.
|
|
$this->filters_added = FALSE;
|
|
|
|
// If we are HTML paging
|
|
if ($this->page) {
|
|
if ($row < $this->po->current_first_item())
|
|
continue;
|
|
elseif ($row > $this->po->current_last_item())
|
|
break;
|
|
}
|
|
|
|
// If we are just listing page_rows items and a summary line as a result of exceeding that
|
|
if ($this->other_col AND $row>$this->page_rows) {
|
|
$this->other += $this->evaluate($o,$this->other_col);
|
|
|
|
// Otherwise rendering our rows
|
|
} else {
|
|
foreach ($this->columns as $col => $label) {
|
|
$c++;
|
|
|
|
$result[$row]['val'][$c] = $this->evaluate($o,$col);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function render() {
|
|
$output = '';
|
|
|
|
// If we need to page the results
|
|
if ($this->page) {
|
|
$this->po = new Pagination(array(
|
|
'total_items'=>count($this->data),
|
|
'items_per_page'=>$this->page_rows,
|
|
));
|
|
|
|
$output .= (string)$this->po;
|
|
|
|
}
|
|
|
|
if ($this->select)
|
|
$output .= Form::open($this->select['form']);
|
|
|
|
$output .= View::factory('table')
|
|
->set('jssort',($this->jssort AND ! $this->page) ? $this->jssort : FALSE)
|
|
->set('other',$this->other)
|
|
->set('th',$this->columns)
|
|
->set('td',$this->process());
|
|
|
|
// Use the javascript paging
|
|
if ($this->jssort AND ! $this->page) {
|
|
Script::factory()
|
|
->type('stdin')
|
|
->data('
|
|
$(document).ready(function() {
|
|
$.extend($.tablesorter.themes.bootstrap, {
|
|
// these classes are added to the table. To see other table classes available,
|
|
// look here: http://twitter.github.com/bootstrap/base-css.html#tables
|
|
table : "table table-striped",
|
|
header : "bootstrap-header", // give the header a gradient background
|
|
footerRow : "",
|
|
footerCells: "",
|
|
icons : "", // add "icon-white" to make them white; this icon class is added to the <i> in the header
|
|
sortNone : "bootstrap-icon-unsorted",
|
|
sortAsc : "icon-chevron-up",
|
|
sortDesc : "icon-chevron-down",
|
|
active : "", // applied when column is sorted
|
|
hover : "", // use custom css here - bootstrap class may not override it
|
|
filterRow : "", // filter row class
|
|
even : "", // odd row zebra striping
|
|
odd : "" // even row zebra striping
|
|
});
|
|
|
|
$("#list-table'.($this->jssort ? '-'.$this->jssort : '').'").tablesorter({
|
|
theme: "bootstrap",
|
|
widthFixed : true,
|
|
headerTemplate : "{content} {icon}", // Add icon for jui theme; new in v2.7!
|
|
widgets: [ "uitheme", "stickyHeaders", "filter" ],
|
|
widgetOptions : {
|
|
stickyHeaders_offset: ".navbar-fixed-top",
|
|
}
|
|
|
|
}).tablesorterPager({
|
|
// target the pager markup - see the HTML block below
|
|
container : $(".pager'.($this->jssort ? '-'.$this->jssort : '').'"),
|
|
output : "{startRow} - {endRow} / {filteredRows} ({totalRows})",
|
|
fixedHeight: true,
|
|
removeRows : false,
|
|
cssGoto : ".gotoPage"
|
|
|
|
});
|
|
});
|
|
');
|
|
|
|
Script::factory()
|
|
->type('file')
|
|
->data('media/vendor/mottie-tablesorter/js/jquery.tablesorter.min.js');
|
|
|
|
Script::factory()
|
|
->type('file')
|
|
->data('media/vendor/mottie-tablesorter/js/jquery.tablesorter.widgets.min.js');
|
|
|
|
Script::factory()
|
|
->type('file')
|
|
->data('media/vendor/mottie-tablesorter/js/jquery.tablesorter.pager.min.js');
|
|
|
|
Style::factory()
|
|
->type('file')
|
|
->data('media/vendor/mottie-tablesorter/css/theme.bootstrap.css');
|
|
Style::factory()
|
|
->type('file')
|
|
->data('media/vendor/mottie-tablesorter/css/jquery.tablesorter.pager.css');
|
|
// @todo The theme should be moved from here, but Controller_Media will need to know about admin requests for media/
|
|
Style::factory()
|
|
->type('file')
|
|
->data('media/theme/baseadmin/css/custom/tablesort.css');
|
|
}
|
|
|
|
if ($this->select) {
|
|
$output .= Form::button('submit','Submit',array('class'=>'btn btn-primary','value'=>$this->select['submit']));
|
|
|
|
foreach ($this->select['hidden'] as $k=>$v)
|
|
$output .= Form::hidden($k,$v);
|
|
|
|
$output .= Form::close();
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
public function select($form,$submit='',array $hidden=array()) {
|
|
$this->select['form'] = $form;
|
|
$this->select['submit'] = $submit;
|
|
$this->select['hidden'] = $hidden;
|
|
|
|
return $this;
|
|
}
|
|
|
|
// @deprecated
|
|
public static function display($data,$rows,array $cols,array $option) {
|
|
$prepend = $headers = array();
|
|
|
|
foreach ($cols as $k=>$v) {
|
|
if (isset($v['label']))
|
|
$headers[$k] = $v['label'];
|
|
if (isset($v['url']))
|
|
$prepend[$k] = array('url'=>$v['url']);
|
|
}
|
|
|
|
return static::factory()
|
|
->data($data)
|
|
->jssort(TRUE)
|
|
->page_items($rows)
|
|
->columns($headers)
|
|
->prepend($prepend);
|
|
|
|
/*
|
|
// This JS is failing, any pressing of select all, unselect all, toggle is propaging up and submitting the form
|
|
|
|
Script::factory()
|
|
->type('stdin')
|
|
->data('
|
|
(function($) {
|
|
// Enable Range Selection
|
|
$.fn.enableCheckboxRangeSelection = function() {
|
|
var lastCheckbox = null;
|
|
var followOn = 0;
|
|
var $spec = this;
|
|
$spec.bind("click", function(e) {
|
|
if (lastCheckbox != null && e.shiftKey) {
|
|
x = y = 0;
|
|
if (followOn != 0) {
|
|
if ($spec.index(lastCheckbox) < $spec.index(e.target)) {
|
|
x = 1 - ((followOn == 1) ? 1 : 0);
|
|
} else {
|
|
y = 1 - ((followOn == -1) ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
$spec.slice(
|
|
Math.min($spec.index(lastCheckbox) - x, $spec.index(e.target)) + 1,
|
|
Math.max($spec.index(lastCheckbox), $spec.index(e.target)) + y
|
|
).attr("checked",function() { return ! this.checked; })
|
|
.parent().parent().toggleClass("selected");
|
|
|
|
followOn = ($spec.index(lastCheckbox) < $spec.index(e.target)) ? 1 : -1;
|
|
} else {
|
|
followOn = 0;
|
|
}
|
|
lastCheckbox = e.target;
|
|
});
|
|
|
|
return $spec;
|
|
};
|
|
|
|
// Enable Toggle, (De)Select All
|
|
$.fn.check = function(mode) {
|
|
// if mode is undefined, use "on" as default
|
|
var mode = mode || "on";
|
|
|
|
switch(mode) {
|
|
case "on":
|
|
$("#select-table tr:not(.head)")
|
|
.filter(":has(:checkbox:not(checked))")
|
|
.toggleClass("selected")
|
|
break;
|
|
case "off":
|
|
$("#select-table tr:not(.head)")
|
|
.filter(":has(:checkbox:checked)")
|
|
.toggleClass("selected")
|
|
break;
|
|
case "toggle":
|
|
$("#select-table tr:not(.head)")
|
|
.toggleClass("selected");
|
|
break;
|
|
}
|
|
|
|
return this.each(function(e) {
|
|
switch(mode) {
|
|
case "on":
|
|
this.checked = true;
|
|
break;
|
|
case "off":
|
|
this.checked = false;
|
|
break;
|
|
case "toggle":
|
|
this.checked = !this.checked;
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
})(jQuery);
|
|
|
|
// Bind our actions
|
|
$(document).ready(function() {
|
|
$("#select-table :checkbox").enableCheckboxRangeSelection();
|
|
$("#select-menu > #toggle").bind("click",function() {
|
|
$("#select-table :checkbox").check("toggle");
|
|
});
|
|
$("#select-menu > #all_on").bind("click",function() {
|
|
$("#select-table :checkbox").check("on");
|
|
});
|
|
$("#select-menu > #all_off").bind("click",function() {
|
|
$("#select-table :checkbox").check("off");
|
|
});
|
|
|
|
// Click to select Row
|
|
$("#select-table tr:not(.head)")
|
|
.filter(":has(:checkbox:checked)")
|
|
.addClass("selected")
|
|
.end()
|
|
.click(function(e) {
|
|
$(this).toggleClass("selected");
|
|
if (e.target.type !== "checkbox") {
|
|
$(":checkbox", this).attr("checked", function() {
|
|
return !this.checked;
|
|
});
|
|
}
|
|
});
|
|
|
|
// Double click to select a row
|
|
$("#select-table tr:not(.head)")
|
|
.dblclick(function(e) {
|
|
window.location = $("a", this).attr("href");
|
|
});
|
|
});
|
|
');
|
|
*/
|
|
}
|
|
|
|
// This enables us to page through many selected items
|
|
// @todo This is currently not usable, since our JS above needs to be fixed to work with tablesorter
|
|
public static function page($key) {
|
|
// We have preference for parameters passed to the action.
|
|
if (is_null($id = Request::current()->param('id'))) {
|
|
// First save our POST id data into the session, so we dont need it when going to each page
|
|
if (isset($_POST['id']) AND is_array($_POST['id']))
|
|
Session::instance()->set('page_table_view'.$key,'id');
|
|
|
|
if ($ids = Session::instance()->get('page_table_view'.$key)) {
|
|
$pag = new Pagination(array(
|
|
'total_items'=>count($ids),
|
|
'items_per_page'=>1,
|
|
));
|
|
|
|
return array($ids[$pag->current_first_item()-1],(string)$pag);
|
|
}
|
|
}
|
|
|
|
// If we get here, then there is no previous data to retrieve.
|
|
return array($id,'');
|
|
}
|
|
}
|
|
?>
|