2013-04-22 05:50:28 +00:00
< ? php defined ( 'SYSPATH' ) or die ( 'No direct access allowed.' );
/**
* This class is for rendering a table of data .
*
* @ package lnApp
2013-10-08 23:24:11 +00:00
* @ category Helpers
2013-04-22 05:50:28 +00:00
* @ author Deon George
* @ copyright ( c ) 2009 - 2013 Deon George
* @ license http :// dev . leenooks . net / license . html
*/
abstract class lnApp_Table {
2013-05-07 01:38:28 +00:00
private $data = NULL ;
private $columns = array (); // Our columns that we need to display
2013-05-14 13:55:30 +00:00
private $filters = array (); // Our datafomrating that we need to use
private $filters_added = FALSE ;
2013-05-07 01:38:28 +00:00
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
2013-11-20 02:26:02 +00:00
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
2013-05-14 13:55:30 +00:00
private $select = array (); // Our select form details
2013-05-07 01:38:28 +00:00
public function __toString () {
2013-06-06 11:25:25 +00:00
try {
2013-11-20 02:26:02 +00:00
return ( string ) $this -> render ();
2013-06-06 11:25:25 +00:00
} catch ( Exception $e ) {
return $e -> getMessage ();
}
2013-05-07 01:38:28 +00:00
}
public function columns ( $cols ) {
$this -> columns = $cols ;
return $this ;
}
public function data ( $data ) {
$this -> data = $data ;
return $this ;
}
private function evaluate ( $d , $key ) {
2013-05-14 13:55:30 +00:00
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 ;
}
2013-04-22 05:50:28 +00:00
if ( is_array ( $d ) AND isset ( $d [ $key ]))
$x = $d [ $key ];
2013-04-25 00:22:36 +00:00
2013-04-22 05:50:28 +00:00
// If the key is a method, we need to eval it
elseif ( preg_match ( '/\(/' , $key ) OR preg_match ( '/-\>/' , $key ))
eval ( " \$ x = \$ d-> $key ; " );
2013-04-25 00:22:36 +00:00
2013-04-22 05:50:28 +00:00
elseif ( preg_match ( '/^__VALUE__$/' , $key ))
$x = $d ;
2013-04-25 00:22:36 +00:00
2013-04-22 05:50:28 +00:00
else
2014-02-17 00:29:11 +00:00
$x = isset ( $d -> { $key }) ? $d -> display ( $key ) : '' ;
// We cant display array values
if ( is_array ( $x ))
$x = array_shift ( $x );
2013-04-22 05:50:28 +00:00
2013-10-08 23:24:11 +00:00
if ( isset ( $this -> prepend [ $key ]) AND $x ) {
2013-05-07 01:38:28 +00:00
foreach ( $this -> prepend [ $key ] as $act => $data ) {
switch ( $act ) {
2013-05-14 13:55:30 +00:00
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 ;
2013-05-07 01:38:28 +00:00
case 'url' : $x = HTML :: anchor ( $data . $x , $x );
2013-05-14 13:55:30 +00:00
break ;
2013-11-08 11:26:58 +00:00
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 ));
2013-05-07 01:38:28 +00:00
}
}
}
2013-11-20 02:26:02 +00:00
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 ));
}
}
}
2013-04-22 05:50:28 +00:00
return $x ;
}
2013-05-07 01:38:28 +00:00
public static function factory () {
return new Table ;
}
2013-05-14 13:55:30 +00:00
/**
* Apply additional display filters to the results
*/
public function filters ( $cols ) {
$this -> filters = $cols ;
return $this ;
}
public function jssort ( $table ) {
$this -> jssort = $table ;
2013-05-07 01:38:28 +00:00
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 ;
}
2013-11-20 02:26:02 +00:00
public function postproc ( $cols ) {
$this -> postproc = $cols ;
return $this ;
}
2013-05-07 01:38:28 +00:00
private function process () {
$result = array ();
$row = 0 ;
foreach ( $this -> data as $o ) {
$row ++ ;
$c = 0 ;
2013-05-14 13:55:30 +00:00
// So our filters are added for this record.
$this -> filters_added = FALSE ;
2013-05-07 01:38:28 +00:00
// 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 ) {
2013-05-14 13:55:30 +00:00
$this -> other += $this -> evaluate ( $o , $this -> other_col );
2013-05-07 01:38:28 +00:00
// Otherwise rendering our rows
} else {
foreach ( $this -> columns as $col => $label ) {
$c ++ ;
$result [ $row ][ 'val' ][ $c ] = $this -> evaluate ( $o , $col );
}
}
}
return $result ;
}
2013-05-14 13:55:30 +00:00
private function render () {
2013-05-07 01:38:28 +00:00
$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 ;
}
2013-05-14 13:55:30 +00:00
if ( $this -> select )
$output .= Form :: open ( $this -> select [ 'form' ]);
2013-05-07 01:38:28 +00:00
$output .= View :: factory ( 'table' )
2013-05-14 13:55:30 +00:00
-> set ( 'jssort' ,( $this -> jssort AND ! $this -> page ) ? $this -> jssort : FALSE )
2013-05-07 01:38:28 +00:00
-> 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
});
2013-05-14 13:55:30 +00:00
$ ( " #list-table'.( $this->jssort ? '-'. $this->jssort : '').' " ) . tablesorter ({
2013-05-07 01:38:28 +00:00
theme : " bootstrap " ,
widthFixed : true ,
headerTemplate : " { content} { icon} " , // Add icon for jui theme; new in v2.7!
widgets : [ " uitheme " , " stickyHeaders " , " filter " ],
2013-05-15 12:21:34 +00:00
widgetOptions : {
stickyHeaders_offset : " .navbar-fixed-top " ,
}
2013-05-07 01:38:28 +00:00
}) . tablesorterPager ({
// target the pager markup - see the HTML block below
2013-05-14 13:55:30 +00:00
container : $ ( " .pager'.( $this->jssort ? '-'. $this->jssort : '').' " ),
2013-05-07 01:38:28 +00:00
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' );
2013-05-15 12:21:34 +00:00
// @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' );
2013-05-07 01:38:28 +00:00
}
2013-05-14 13:55:30 +00:00
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 ();
}
2013-05-07 01:38:28 +00:00
return $output ;
}
2013-05-14 13:55:30 +00:00
public function select ( $form , $submit = '' , array $hidden = array ()) {
$this -> select [ 'form' ] = $form ;
$this -> select [ 'submit' ] = $submit ;
$this -> select [ 'hidden' ] = $hidden ;
return $this ;
}
// @deprecated
2013-04-22 05:50:28 +00:00
public static function display ( $data , $rows , array $cols , array $option ) {
2013-05-07 01:38:28 +00:00
$prepend = $headers = array ();
2013-04-22 05:50:28 +00:00
2013-05-07 01:38:28 +00:00
foreach ( $cols as $k => $v ) {
if ( isset ( $v [ 'label' ]))
$headers [ $k ] = $v [ 'label' ];
if ( isset ( $v [ 'url' ]))
$prepend [ $k ] = array ( 'url' => $v [ 'url' ]);
}
2013-04-22 05:50:28 +00:00
2013-12-26 10:39:49 +00:00
return self :: factory ()
2013-05-07 01:38:28 +00:00
-> data ( $data )
-> jssort ( TRUE )
-> page_items ( $rows )
-> columns ( $headers )
-> prepend ( $prepend );
2013-04-22 05:50:28 +00:00
2013-05-07 01:38:28 +00:00
/*
// This JS is failing, any pressing of select all, unselect all, toggle is propaging up and submitting the form
2013-04-25 00:22:36 +00:00
Script :: factory ()
-> type ( 'stdin' )
-> data ( '
2013-04-22 05:50:28 +00:00
( 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 " );
});
});
2013-04-25 00:22:36 +00:00
' );
2013-05-07 01:38:28 +00:00
*/
2013-04-22 05:50:28 +00:00
}
2013-05-07 01:38:28 +00:00
// 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
2013-04-22 05:50:28 +00:00
public static function page ( $key ) {
// We have preference for parameters passed to the action.
if ( is_null ( $id = Request :: current () -> param ( 'id' ))) {
2013-05-07 01:38:28 +00:00
// First save our POST id data into the session, so we dont need it when going to each page
2013-04-22 05:50:28 +00:00
if ( isset ( $_POST [ 'id' ]) AND is_array ( $_POST [ 'id' ]))
2013-05-07 01:38:28 +00:00
Session :: instance () -> set ( 'page_table_view' . $key , 'id' );
2013-04-22 05:50:28 +00:00
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 , '' );
}
}
?>