Skip to content

Instantly share code, notes, and snippets.

@son0fhobs
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save son0fhobs/becf6838c178b3760ee0 to your computer and use it in GitHub Desktop.
Save son0fhobs/becf6838c178b3760ee0 to your computer and use it in GitHub Desktop.
HTML Table Generator
<?php
/*
Advantages
Add data in any format. Associative, numberic, multiple rows, single rows, etc
Allows to add data and display in two methods.
Flexibility, extensibility. Any attrs, rows, cells, table, even/odd, etc
Filter rows for flexibility in attrs (row thirds, etc), as well as filter data and attrs based on data
Option - to show empty sections? Or only specific empty sections? - most notably datatables requiring header
Tools
CSS, classes, attrs
- http://tablestyler.com/
Php classes
Codeigniter - best - https://ellislab.com/codeigniter/user-guide/libraries/table.html
https://github.com/naomik/htmlgen
http://www.dyn-web.com/php/table_class/code.php
http://www.phpclasses.org/package/6089-PHP-Display-data-from-arrays-in-HTML-tables-.htmlgen
http://www.phpclasses.org/package/667-PHP-Class-for-easy-composing-complex-html-tables.html
*/
// col groups?
// row groups?
// Data input options
// add one row at a time
$data = array('first cell', 'second cell', 'third cell'); // add single row numerically
// each one, cell with data then attrs
$data = array(
array( 'data' => 'first cell', 'attrs' => array()),
array( 'data' => 'second cell', 'attrs' => array()),
array( 'data' => 'third cell', 'attrs' => array()),
);
// multiple rows
// without attrs
$data = array(
array('first cell, 'second cell', 'third cell'), // add multiple rows, numerically
array('first cell, 'second cell', 'third cell')
);
// with attrs - same setup
$data = array(
array(
'tr-attrs' => array(),
'td' => array( // this can be added without attrs as numeric array
array( 'data' => 'first cell', 'attrs' => array() ), // each one, cell with data then attrs
array( 'data' => 'second cell', 'attrs' => array()),
array( 'data' => 'third cell', 'attrs' => array()),
)
),
array() // next row
);
// filtering data
// check for most complicated full first by checking keys. Then check simpler and simpler.
// display warnings if improper keys? Make assumptions?
// associative arrays?
// - set order, if not take first row to setup order.
// - use keys as classes?
/*
table->generate() => display
table->set_caption()
table->set_heading()
table->add_row()
table->add_rows() // my custom
table->make_columns() // numeric array, broken up by number. Not a fan.
table->clear() // remove content.
table->function = 'htmlspecialchars'; // filter function.
My methods
table->get_table() // return, don't display
table->add_data() // add multiple rows, data, flexibile.
table->add_footer()
table->add_header()
table->set_assoc_order() // if associative array, set order of sells. Defaults to first row.
table->set_default_attrs();
*/
public $options = array(
'default_section_attrs' => true, // use default attributes
'append_sections_attrs' => true,
'debug' => false,
);
private $section_attrs => array();
public $errors = '';
private $thead_data = array();
private $tbody_data = array();
private $tfoot_data = array();
private $caption = '';
public function __construct($options = array(), $data = array(), $sections_attrs = array()){
if($options){
$this->options = array_merge($this->options, $options);
}
$this->clear_sections_attrs(); // ensure proper keys
if($this->options['default_section_attrs']){
$this->reset_sections_attr_defaults();
}
if($sections_attrs){
$this->set_sections_attrs($sections_attrs, $this->options['append_sections_attrs']);
}
if($data){
$this->add_data($data);
}
}
private function reset_sections_attrs_defaults(){
$this->section_attrs => array(
'table-attrs' => array(
'class' => 'table-striped', // dataTable, table-striped, table-bordered, table-hovered, 'table-condensed', all bootstrap
'id' => ''
),
'row-attrs' => array( 'class' => 'row' ),
'row-even-attrs' => array( 'class' => 'even row-even' ),
'row-odd-attrs' => array( 'class' => 'odd row-odd' ),
'col-even-attrs' => array( 'class' => 'col-even' ),
'col-odd-attrs' => array( 'class' => 'col-odd' ),
);
}
public function clear_sections_attrs(){
$this->section_attrs => array(
'table-attrs' => array(),
'caption-attrs' => array(),
'thead-attrs' => array(),
'tbody-attrs' => array(),
'tfoot-attrs' => array(),
'row-attrs' => array(),
'row-even-attrs' => array(),
'row-odd-attrs' => array(),
'col-even-attrs' => array(),
'col-odd-attrs' => array(),
);
}
public function clear_section_attrs($reset_defaults = true){
if($reset_defaults){
$this->reset_sections_attr_defaults();
}else{
$this->clear_sections_attrs();
}
}
// changing default attrs
public function set_sections_attrs($attrs, $if_append = true){ // set multiple attrs at the same time?
// $if_append, add to default attrs. Otherwise overwrite.
foreach($attrs as $section => $section_attrs){
if(isset($this->section_attrs[$section])){
$this->set_section_attrs($section, $section_attrs, $append){
}else{
$this->errors .= 'method set_section_attrs, $attrs improperly formatted. Ensure matches default_attrs array keys. ';
}
}
}
public function set_section_attrs($section, $attrs, $append = true){
if( isset($section_attrs[$section]) ){
foreach($attrs as $attr => $val){
if($append){
$this->section_attrs[$section][$attr] .= $val;
}else{
$this->section_attrs[$section][$attr] = $val;
}
}
}
} // change one attr only
public function clear($section = 'all'){
$this->clear_data($section);
}
public function clear_data($section = 'all', $section_attrs = 'defaults'){
if($section == 'thead' || $section == 'header'){
$this->thead_data[] = array();
}else if($section == 'tfoot' || $section == 'footer'){
$this->tfoot_data[] = array();
}else if($section == 'tbody' || $section == 'body'){
$this->tbody_data[] = array();
}else if($section == 'caption'){
$this->caption = '';
}else if($section == 'all'){
$this->thead_data[] = array();
$this->tfoot_data[] = array();
$this->tbody_data[] = array();
$this->caption = '';
}else{
$this->errors += 'clear_data $section unrecognized. Inputed: '.$section.'. Default to clearing all. ';
$this->thead_data[] = array();
$this->tfoot_data[] = array();
$this->tbody_data[] = array();
$this->caption = '';
}
}
public function set_caption($text){
$this->add_data($text, 'caption');
}
public function add_caption($text){
$this->add_data($text, 'caption');
}
public function add_footer($data){
$this->add_data($data, 'tfoot');
}
public function add_header($data){
$this->add_data($data, 'thead');
}
public function add_row($data){
$this->add_data($data, 'tbody');
}
public function add_rows($data){
$this->add_data($data, 'tbody');
}
public function add_data($data, $section = 'tbody'){
if($data != 'caption'){
$user_data = format_input_data($data);
}else{
$user_data = $data;
}
if($section == 'thead' || $section == 'header'){
$this->thead_data[] = $user_data;
}else if($section == 'tfoot' || $section == 'footer'){
$this->tfoot_data[] = $user_data;
}else if($section == 'tbody' || $section == 'body'){
$this->tbody_data[] = $user_data;
}else if($section == 'caption'){
$this->caption = $user_data;
}else{
$this->errors += 'set_data $section unrecognized. Inputed: '.$section.'. Default adding to tbody. ';
$this->tbody_data[] = $user_data;
}
}
private function single_dimension_format($array){
// single row
$row = array(
'tr-attrs' => array(),
'td' => array()
);
foreach($array as $cell){
$row['td']['data'] => $cell;
$row['td']['attrs'] => array(); // defaults
}
return $row;
}
private function format_single_row_td_data($array){
$row = array(
'tr-attrs' => array(), // default tr attrs
'td' => array()
);
foreach($array as $cell_data){
$row['td']['data'] = (is_array($cell_data) && isset($cell_data['data'])) ? $cell_data['data'] : $cell_data; // improve me - check all for data, if array, if not?
$row['td']['attrs'] = ($cell_data['attrs']) ? $cell_data['attrs'] : array(); // defaults
}
return $row;
}
private function format_single_row_tr_attrs($array){
$row = array(
'tr-attrs' => $array['tr-attrs'],
'td' => array();
);
foreach($array['td'] as $cell){
$row['td'][] = array(
'attrs' => $cell['attrs'],
'data' => $cell['data']
);
}
return $row;
}
// There has to be a better way. More reliable and better pattern oriented.
private function format_input_data($array){
// Logic
/*
if numeric, and single dimension, each row.
if numeric then assoc - check which kind assoc. tr-attrs, td,
if numeric, numeric, then 'attr' and 'data' then sinle row with td attrs and data
if numeric, and numeric again, then not 'attr' or 'data' - adding multiple rows. Detect which kind
if only two numeric no assoc - multiple rows
*/
if(!is_array($data)){
// return, not array
}
// if not associative then not attrs on first elems
if(!$this->is_assoc($array) && !isset($array['tr-attrs']) ){
/* Each of these should be in it's own function */
// if more than single row or cell with attrs
if(!$this->is_multi_dimension($array)){
$row = $this->single_dimension_format($array);
}else{
// multidimensional rows
// each cell with data and attrs
if(isset($array[0]['data']) && !isset($array['tr-attrs'])){
$row = $this->format_single_row_td_data($array);
}else if(isset($array['tr-attrs'])){
// on row, tr-attrs,
$row = $this->format_single_row_tr_attrs($array);
}else if( isset($array[0]) && is_array($array[0]) ){
// multidimensional rows?
// multiple rows
// ???
// $formated_data
$formated_data = array(); // this is all the data the user added
foreach($array as $row_array){
$formated_data[] = $this->format_input_data($row_array);
}
// all inputed data filtered, return
return $formatted_data;
}
}
}else{
// associative array, how deal with?
}
return $row;
}
public function generate(){
$this->display_table();
}
public function display_table(){
echo $this->get_table();
}
public function get_table(){
$html = '';
$html .= '<table '.attrs_to_html($this->section_attrs['table-attrs']).'>';
$html .= get_section_html('caption');
$html .= get_section_html('thead');
$html .= get_section_html('tbody');
$html .= get_section_html('tfoot');
$html .= '</table>';
return $html;
}
public function get_section_html($section == 'tbody'){
$html = '';
if( $section == 'caption' && $this->caption){
$html .= '<caption '.attrs_to_html($this->section_attrs['caption-attrs']).'>'.$this->caption.'</caption>';
}else if(($section == 'thead' || $section == 'header') && $this->thead_data){
$html .= '<thead '.attrs_to_html($this->section_attrs['thead-attrs']).'>';
$html .= $this->section_to_html($this->thead_data, 'thead');
$html .= '</thead>';
}else if(($section == 'tbody' || $section == 'body') && $this->tbody_data){
$html .= '<tbody '.attrs_to_html($this->section_attrs['tbody-attrs']).'>';
$html .= $this->section_to_html($this->tbody_data, 'tbody');
$html .= '</tbody>';
}else if(($section == 'tfoot' || $section == 'footer') && $this->tfoot_data){
$html .= '<tfoot '.attrs_to_html($this->section_attrs['tfoot-attrs']).'>';
$html .= $this->section_to_html($this->tfoot_data, 'tfoot');
$html .= '</tfoot>';
}else{
// error fix me
}
}
private function section_to_html($data, $section = 'tbody'){
$html_data = '';
$tag = ($section == 'thead') ? 'th' : 'td';
$row_i=0;
foreach($data as $row){
$html_data .= $this->row_to_html($row, $row_i, $tag);
$row_i++;
}
return $html_data;
}
/*
'row-attrs' => array( 'class' => 'row' ),
'row-even-attrs' => array( 'class' => 'even row-even' ),
'row-odd-attrs' => array( 'class' => 'odd row-odd' ),
'col-even-attrs' => array( 'class' => 'col-even' ),
'col-odd-attrs' => array( 'class' => 'col-odd' ),
*/
private function row_to_html($row, $row_i, $tag = 'td'){
$html_row = '';
$tr_attrs = '';
$tr_attrs .= attrs_to_html($row['tr-attrs']);
$tr_attrs .= attrs_to_html($this->section_attrs['row-attrs']);
$tr_attrs .= ($i%$row_i) ? attrs_to_html($this->section_attrs['row-odd-attrs']) : attrs_to_html($this->section_attrs['row-even-attrs']);
$td_i = 0;
$td_attrs = ''; // col even, col odd
// '<tr attrs><td attrs></td></tr>'
$html_row .= '<tr '.$tr_attrs.'>';
foreach($row['td'] as $cell){
$td_attrs = attrs_to_html($cell['attrs']);
$td_attrs .= ($i%$td_i) ? attrs_to_html($this->section_attrs['col-odd-attrs']) : attrs_to_html($this->section_attrs['col-even-attrs']);
$html_row .= '<'.$tag.' '.$td_attrs.'>'.$cell['data'].'</'.$tag.'>';
$td_i++;
}
$html_row .= '</tr>';
return $html_row;
}
private function attrs_to_html($attrs){
if(!$attrs || !is_array($attrs)){
return $attrs;
}
$attrs_html = '';
foreach($attrs as $key => $val){
$attrs_html = '"'.$key.'"="'.$val.'"';
}
return $attrs_html;
}
// helper functions
private function is_assoc($array) {
return (bool)count(array_filter(array_keys($array), 'is_string'));
}
private function is_multi_dimension($array){
foreach($array as $key => $val){
if(is_array($val)){
return true;
}
}
return false;
}
// Defaults = what to do with empty vals?
// This is old data, examples from previous project
function setup_datatables($datatables, $cols, $col_names = array(), $tr_class_filter = '', $td_class_filter = '', $row_data_filter = '', $complete_row_filter = ''){
$inactive_row_classes = array(
// 0 => 'error',
// 1 => 'error',
// 2 => 'pending'
);
// if it has any of these columns, then it's the pricing table
$pricing_table_cols = array('monthly', 'up_front', 'pay_as_go'); // 'labels', 'custom'
$is_pricing_table = (array_intersect($pricing_table_cols, $cols)) ? true : false;
$html = '';
$max_cols = count($cols);
// Add the headers, unless set to false
if($col_names != false){
if(!$col_names){
$col_names = $cols;
}
$html = '<thead><tr>';
// Add headers
for($c=0;$c<$max_cols;$c++){
$html .= '<th class="'.$cols[$c].'">'.$col_names[$c].'</th>';
}
$html .= '</tr></thead>';
}
$html .= '<tbody>';
$max = count($datatables);
// loop through rows
for($r=0;$r<$max;$r++){
$tr_class = '';
if($tr_class_filter && is_callable($tr_class_filter)){
// pass entire row to get tr class. given default classes row-even, etc, pass in current
$tr_class = call_user_func($tr_class_filter, $tr_class, $datatables[$r]);
}
if($row_data_filter && is_callable($row_data_filter)){
// pass entire row to get tr class. given default classes row-even, etc, pass in current
$datatables[$r] = call_user_func($row_data_filter, $datatables[$r]);
}
// pass entire row, or one cell at a time?
// one cell.
// $row_data_filter = '', $complete_row_filter = ''
// overview classes
$row_class = '';
if( isset($datatables[$r]['total_classes']) && isset($inactive_row_classes[$datatables[$r]['total_classes']]) ){
$row_class = $inactive_row_classes[$datatables[$r]['total_classes']];
}
$html .= '<tr class="'.trim($row_class.' '.$tr_class).'" >';
// loop through columns - cells.
for($c=0;$c<$max_cols;$c++){
// make sure val exists, if not, add blank.
$cell_val = (isset($datatables[$r][$cols[$c]])) ? $datatables[$r][$cols[$c]] : '';
$td_class = '';
if($td_class_filter && is_callable($td_class_filter)){
// pass key, val, to get td class
$td_class = call_user_func($td_class_filter, $td_class, $cols[$c], $cell_val);
}
// add label to td class to highlight it
$label_class = ($is_pricing_table && $cell_val && detect_dom_radio_checked($cell_val)) ? 'label' : '';
$html .= '<td class="'.trim( $cols[$c].' '.$label_class.' '.$td_class ).'">'.$cell_val.'</td>';
}
$html .= '</tr>';
}
$html .= '</tbody>';
// <table> before function to help clarify for css
return $html;
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment