Subversion Repositories web.creative

Rev

Blame | Last modification | View Log | Download

<?php namespace ProcessWire;

/**
 * Class MarkupAdminDataTable
 * 
 * @method string render()
 * 
 * @property-read array $headerRow
 * @property-read array $footerRow
 * @property-read array $rows
 * @property-read array $rowClasses
 * @property-read array $rowAttrs
 * @property-read array $actions
 * @property bool $encodeEntities
 * @property bool $sortable
 * @property bool $resizable
 * @property string $class
 * @property string $caption
 * @property bool $responsive
 * @property array $settings
 * @property string $id
 * 
 */

class MarkupAdminDataTable extends ModuleJS {
  
  public static function getModuleInfo() {
    return array(
      'title' => 'Admin Data Table',
      'summary' => 'Generates markup for data tables used by ProcessWire admin',
      'version' => 107,
      'permanent' => true,
    );
  }

  const responsiveNo = 0; // responsive off
  const responsiveYes = 1; // each td becomes 1-row, each 2 columns with th + td side-by-side
  const responsiveAlt = 2; // each td becomes 1-row, with th + td stacked on top of each other

  /**
   * Number of table instances, for unique id attributes
   * 
   * @var int
   * 
   */
  static protected $instanceCnt = 0;

  /**
   * Table rows
   * 
   * @var array
   * 
   */
  protected $rows = array();
  
  protected $headerRow = array();
  
  protected $footerRow = array();
  
  protected $rowClasses = array();
  
  protected $rowAttrs = array();
  
  protected $actions = array();

  /**
   * Initialize module and default settings
   * 
   */
  public function init() {
    
  
    // defaults for settings that are typically set globally for all tables
    $defaults = array(
      'class' => 'AdminDataTable AdminDataList',
      'addClass' => '', 
      'responsiveClass' => 'AdminDataTableResponsive',
      'responsiveAltClass' => 'AdminDataTableResponsiveAlt',
      'sortableClass' => 'AdminDataTableSortable',
      'resizableClass' => 'AdminDataTableResizable',
      'loadStyles' => true,
      'loadScripts' => true, 
    );
  
    // settings set globally for all tables (when present)
    $settings = $this->wire('config')->MarkupAdminDataTable;
    
    if(empty($settings)) {
      $settings = $defaults;
    } else {
      $settings = array_merge($defaults, $settings); 
    }
  
    if(!empty($settings['sortableClass'])) {
      $this->modules->get("JqueryTableSorter");
    }
    
    $this->loadStyles = $settings['loadStyles'];
    $this->loadScripts = $settings['loadScripts'];
    
    $this->set('encodeEntities', true);
    $this->set('sortable', true);
    $this->set('resizable', false);
    $this->set('class', ''); // extra class(es), populated by addClass() method
    $this->set('caption', '');
    $this->set('responsive', self::responsiveYes);
    $this->set('settings', $settings);
    $this->set('id', '');
    
    parent::init();
  }

  /**
   * Get property
   * 
   * @param string $key
   * @return mixed|null
   * 
   */
  public function get($key) {
    if($key === 'rows' || $key === 'headerRow' || $key === 'footerRow') return $this->$key;
    if($key === 'rowClasses' || $key === 'rowAttrs' || $key === 'actions') return $this->$key;
    return parent::get($key);
  }

  /**
   * Set the header row for the table
   * 
   * @param array $a Array of header row labels (strings)
   * @return $this
   * 
   */
  public function headerRow(array $a) {
    $this->headerRow = $a; 
    return $this; 
  }

  /**
   * Set the footer row for the table
   * 
   * @param array $a Array of footer row labels (strings)
   * @return $this
   * 
   */
  public function footerRow(array $a) {
    $this->footerRow = $this->setupRow($a);
    return $this;
  }

  /**
   * Add a row to the table
   * 
   * @param array $a Array of columns that will each be a `<td>`, where each element may be one of the following:
   *   - `string`: converts to `<td>string</td>`
   *   - `array('label' => 'url')`: converts to `<td><a href='url'>label</a></td>`
   *   - `array('label', 'class')`: converts to `<td class='class'>label</td>`
   * @param array $options Optionally specify any one of the following:
   *   - separator (bool): specify true to show a stronger visual separator above the column
   *   - class (string): specify one or more class names to apply to the `<tr>`
   *   - attrs (array): array of attr => value for attributes to add to the `<tr>`
   * @return $this
   * 
   */
  public function row(array $a, array $options = array()) {
    $defaults = array(
      'separator' => false,
      'class' => '', 
      'attrs' => array(),
    );
    $options = array_merge($defaults, $options);
    $n = count($this->rows);
    if($options['separator']) {
      $options['class'] .= ($options['class'] ? ' ' : '') . 'AdminDataListSeparator';
    }
    $this->rows[$n] = $this->setupRow($a);
    if(!empty($options['class'])) $this->rowClasses[$n] = $options['class'];
    if(!empty($options['attrs'])) $this->rowAttrs[$n] = $options['attrs'];
    return $this; 
  }

  /**
   * Setup/prepare a table row for rendering (internal)
   * 
   * @param array $a
   * @return array
   * 
   */
  protected function setupRow(array $a) {
    $row = array();

    foreach($a as $k => $v) {
      if(is_string($k)) {
        // Associative arrays get converted to: 
        // Anchor Text => URL
        $v = "<a href='$v'>" . $this->encode($k) . "</a>";
      } else if(is_array($v)) {
        // class name is specified in $v[1]
        foreach($v as $kk => $vv) {
          $v[$kk] = $this->encode($vv);
        }
      } else {
        $v = $this->encode($v); 
      }
      $row[] = $v; 
    }
    return $row;
  }

  /**
   * Add action(s) button underneath the table
   * 
   * @param array $action Array in format array('button-label' => 'url')
   * @return $this
   * 
   */
  public function action(array $action) {
    foreach($action as $label => $url) { 
      $this->actions[$label] = $url;
    }
    return $this;
  }

  /**
   * Render the table
   * 
   * @return string
   * 
   */
  public function ___render() {

    $tableClass = trim($this->settings('class') . ' ' . $this->class); 
    if($this->responsive == self::responsiveYes) {
      $tableClass .= ' ' . $this->settings('responsiveClass'); 
    } else if($this->responsive == self::responsiveAlt) {
      $tableClass .= ' ' . $this->settings('responsiveClass') . ' ' . $this->settings('responsiveAltClass'); 
    }
    if($this->sortable) $tableClass .= ' ' . $this->settings('sortableClass');
    if($this->resizable) {
      $tableClass .= ' ' . $this->settings('resizableClass');
      /** @var JqueryTableSorter $tableSorter */
      $tableSorter = $this->modules->get('JqueryTableSorter');
      $tableSorter->use('widgets');
    }
    if($this->settings('addClass')) $tableClass .= ' ' . $this->settings('addClass');
    $out = '';
    $maxCols = 0;
    $id = $this->id ? $this->id : "AdminDataTable" . (++self::$instanceCnt);

    if(count($this->rows)) { 
      $out = "\n<table id='$id' class='$tableClass'>";

      if($this->caption) $out .= "\n\t<caption>{$this->caption}</caption>";

      if(count($this->headerRow)) {
        $out .= "\n\t<thead>\n\t<tr>";
        foreach($this->headerRow as $th) {
          $class = '';
          if(is_array($th)) list($th, $class) = $th;
          $th = $this->encode($th);
          if($class) $class = " class='" . $this->encode($class) .  "'";
          $out .= "\n\t\t<th$class>$th</th>";
          $maxCols++;
        }
        $out .= "\n\t</tr>\n\t</thead>";
      }

      if(count($this->footerRow)) {
        $out .= "\n\t<tfoot>\n\t<tr>";
        foreach($this->footerRow as $td) {
          $out .= "\n\t\t\t<td>$td</td>"; 
        }
        $out .= "\n\t</tr>\n\t</tfoot>";
      }

      $out .= "\n\t<tbody>";
      foreach($this->rows as $n => $row) {
        $cols = count($row);
        if($cols > $maxCols) $maxCols = $cols; 
        if($cols < $maxCols) for(; $cols < $maxCols; $cols++) $row[] = '&nbsp;';
        $attrs = isset($this->rowAttrs[$n]) ? $this->rowAttrs[$n] : array();
        if(isset($this->rowClasses[$n])) $attrs['class'] = $this->rowClasses[$n];
        $attrStr = '';
        foreach($attrs as $attrName => $attrVal) {
          $attrStr .= " $attrName='" . $this->wire('sanitizer')->entities($attrVal) . "'";
        }
        $out .= "\n\t\t<tr$attrStr>";
        foreach($row as $td) {
          $class = '';
          if(is_array($td)) list($td, $class) = $td;
          if(strlen($td) == 0 || $td === '&nbsp;') $class .= ($class ? ' ' : '') . 'blank';
          if($class) $class = " class='$class'";
          $out .= "\n\t\t\t<td$class>$td</td>"; 
        }
        $out .= "\n\t\t</tr>";
      }
      $out .= "\n\t</tbody>";
      $out .= "\n</table>";
      
      if($this->responsive && strpos($this->settings('responsiveClass'), 'AdminDataTableResponsive') === 0) {
        $out .= "\n<script>if(typeof AdminDataTable != 'undefined') AdminDataTable.initTable($('#$id'));</script>";
      }
    }

    if(count($this->actions)) {
      $out .= "\n<p>";
      foreach($this->actions as $label => $url) {
        /** @var InputfieldButton $button */
        $button = $this->modules->get("InputfieldButton"); 
        $button->href = $url;
        $button->value = $label;
        $out .= $button->render();
      }
      $out .= "\n</p>";
    }
    
    return $out; 
  }

  /**
   * Entity encode string (when entity encoding enabled)
   * 
   * @param string $str
   * @return string
   * 
   */
  protected function encode($str) {
    if(!$this->encodeEntities) return $str; 
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8'); 
  }

  /**
   * Set whether or not entity encoding is enabled
   * 
   * @param bool $encodeEntities
   * 
   */
  public function setEncodeEntities($encodeEntities = true) {
    $this->encodeEntities = $encodeEntities ? true : false; 
  }

  /**
   * Set class(es) to add to table
   * 
   * @param string $class
   * 
   */
  public function setClass($class) {
    $this->class = $this->encode($class); 
  }

  /**
   * Add a class to the table (without replacing existing ones)
   * 
   * @param string $class
   * 
   */
  public function addClass($class) {
    $this->class = trim($this->class . " " . $this->encode($class)); 
  }

  /**
   * Set whether or not table is sortable
   * 
   * @param bool $sortable
   * 
   */
  public function setSortable($sortable) {
    $this->sortable = $sortable ? true : false;
  }

  /**
   * Set whether or not table is resizable
   * 
   * @param bool $resizable
   * 
   */
  public function setResizable($resizable) {
    $this->resizable = $resizable ? true : false;
  }

  /**
   * Set table caption
   * 
   * @param string $caption
   * 
   */
  public function setCaption($caption) {
    $this->caption = $this->encode($caption); 
  }

  /**
   * Set table id attribute
   * 
   * @param string $id
   * 
   */
  public function setID($id) {
    $this->id = $id; 
  }

  /**
   * Set the responsive mode of this table
   * 
   * Default behavior is responsiveYes. Specify false or 0 to disable responsive.
   * Or specify MarkupAdminDataTable::responsiveAlt for stacked th + td. 
   * 
   * @param int|bool $responsive
   * 
   */
  public function setResponsive($responsive = true) {
    $this->responsive = (int) $responsive; 
  }

  /**
   * Get or set an internal setting 
   * 
   * @pw-internal
   * 
   * @param string $key Setting to get or set
   * @param mixed $value Optional value to set
   * @return string|int|array|null|MarkupAdminDataTable
   * 
   */
  public function settings($key, $value = null) {
    $settings = parent::get('settings');
    if(is_null($value)) {
      return isset($settings[$key]) ? $settings[$key] : null;
    } else {
      $settings[$key] = $value;
      parent::set('settings', $settings);
      return $this;
    }
  }

  public function isSingular() {
    return false; 
  }

  public function isAutoload() {
    return false; 
  }

}