Subversion Repositories web.active

Rev

Rev 1 | Blame | Compare with Previous | Last modification | View Log | Download

<?php namespace ProcessWire;

/**
 * ProcessWire Checkboxes Inputfield
 * 
 * ProcessWire 3.x, Copyright 2021 by Ryan Cramer
 * https://processwire.com
 * 
 * @property bool $table Whether or not to display as a table
 * @property string $thead Pipe "|" separated list of table headings. Do the same for the addOption() labels. 
 * @property int|bool $optionColumns Specify 1 for inline list of options, or qty of columns to list options in.
 * @property string $optionWidth Alternative to optionColumns, specify option width like '222px', '22em', etc. or '1' for auto. 3.0.184+
 * 
 */

class InputfieldCheckboxes extends InputfieldSelectMultiple implements InputfieldHasArrayValue {

  public static function getModuleInfo() {
    return array(
      'title' => __('Checkboxes', __FILE__), // Module Title
      'summary' => __('Multiple checkbox toggles', __FILE__), // Module Summary
      'version' => 108,
      'permanent' => true, 
    );
  }

  /**
   * Init
   * 
   */
  public function init() {
    $this->set('table', false); 
    $this->set('thead', ''); 
    $this->set('optionColumns', 0);
    $this->set('optionWidth', '');
    parent::init();
    $this->set('size', null); // cancel 'size' attribute used by select multiple
  }

  /**
   * Render 
   * 
   * @return string
   * 
   */
  public function ___render() { 
    
    $sanitizer = $this->wire()->sanitizer;

    $this->checkDefaultValue();
    $out = '';
    $table = null;
    $columns = (int) $this->optionColumns;
    $inline = $columns === 1 || $columns > 10; 
    $liAttr = '';
    $ulClass = '';
    $inputClass = $sanitizer->entities($this->attr('class'));
    $entityEncode = $this->getSetting('entityEncodeText') === false ? false : true;
    $options = $this->getOptions();
    $optionWidth = $this->optionWidth ? $this->getOptionWidthCSS($this->optionWidth, $options) : '';
    
    if($this->table) {
      /** @var MarkupAdminDataTable $table */
      $table = $this->modules->get("MarkupAdminDataTable");
      $table->setEncodeEntities(false);
      $table->setSortable(false);
      $table->addClass('pw-no-select');
      if($this->thead) $table->headerRow(explode('|', htmlspecialchars($this->thead, ENT_QUOTES, 'UTF-8')));
      
    } else if($optionWidth) {
      $liAttr = " style='width:$optionWidth'";
      $ulClass = 'InputfieldCheckboxesWidth';

    } else if($columns) {

      if($inline) {
        $ulClass = 'InputfieldCheckboxesFloated';
      } else {
        $liWidth = round(100 / $columns)-1;  // 1% padding-right added from stylesheet
        $liAttr = " style='width: {$liWidth}%;'";
        $ulClass = 'InputfieldCheckboxesColumns';
      }

      $classes = InputfieldWrapper::getClasses();
      $ulClass .= " " . $classes['list_clearfix'];

    } else {
      $ulClass = 'InputfieldCheckboxesStacked';
    }

    if(!$table) $out = "<ul class='$ulClass'>";

    foreach($options as $key => $value) {
      $checked = '';

      if($this->isOptionSelected($key)) $checked = " checked='checked'";
      
      $id = $sanitizer->name($key);
      if(!strlen(trim($id, '_'))) $id = trim(base64_encode($key), '=/.');
      $id = $this->id . '_' . $id;

      $attrs = $this->getOptionAttributes($key);
      $disabled = empty($attrs['disabled']) ? '' : " disabled='disabled'";
      unset($attrs['checked'], $attrs['selected'], $attrs['disabled']); 
      $attrs = $this->getOptionAttributesString($attrs);
      if($attrs) $attrs = ' ' . $attrs; 

      if($entityEncode) $value = $this->entityEncode($value, true); 

      $input = 
        "<label$attrs>" . 
        "<input$checked$disabled " . 
        "type='checkbox' " . 
        "name='{$this->name}[]' " . 
        "id='$id' " . 
        "class='$inputClass' " . 
        "value='" . htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . "' />"; 

      if($table) {
        $value = explode("|", nl2br($value));
        $value[0] = "$input<span class='pw-no-select'>$value[0]</span></label>";
        $table->row($value); 
      } else {
        $out .= "<li$liAttr>$input<span class='pw-no-select'>$value</span></label></li>";
      }
      
    }

    if($table) $out .= $table->render();
      else $out .= "</ul>";

    return $out; 

  }

  /**
   * Get CSS width and unit for option width
   * 
   * @param string|int $optionWidth
   * @param array $options
   * @return string
   * 
   */
  public function getOptionWidthCSS($optionWidth, array &$options) {
    if(!$optionWidth) return '';
    if(!preg_match('/^(\d+)\s*([a-z]{1,4}|%|);?$/i', $optionWidth, $matches)) return '';
    $unit = empty($matches[2]) ? 'px' : $matches[2];
    $optionWidth = $matches[1] . $unit;
    if($optionWidth === '1px') { // 1 or 1px means "calculate at runtime"
      $optionWidth = 10;
      foreach($options as $label) {
        if(strlen($label) > $optionWidth) $optionWidth = strlen($label);
      }
      if($optionWidth > 20) {
        // use without adjustment
      } else if($optionWidth > 10) {
        $optionWidth += 2; 
      } else {
        $optionWidth = 10; // minimum option width
      }
      $optionWidth .= 'ch';
    }
    return $optionWidth;
  }

  public function set($key, $value) {
    if($key == 'optionColumns') {
      $value = (int) $value;
      if($value < 0) $value = 0;
      if($value > 10) $value = 10;
    }
    return parent::set($key, $value); 
  }

  public function ___getConfigInputfields() {
    $modules = $this->wire()->modules;
    $inputfields = parent::___getConfigInputfields();
    
    /** @var InputfieldInteger $f */
    $f = $modules->get('InputfieldText');
    $f->label = $this->_('Option column width');
    $f->description = 
      $this->_('Use this setting to have options display in fixed width columns that float or collapse according to viewport width.') . ' ' . 
      $this->_('Enter a CSS width number and unit such as `100px` or `10em`, for example. If no unit is specified, `px` (pixels) is assumed.') . ' ' . 
      $this->_('Enter the number `1` to automatically calculate an appropriate width at runtime.'); 
    $f->notes = $this->_('For best appearance, enter a width that can fit the longest option without word wrapping, or enter `1` to for auto width.');
    $f->attr('name', 'optionWidth');
    $f->attr('value', $this->optionWidth);
    if(!$this->optionWidth) $f->collapsed = Inputfield::collapsedYes;
    $inputfields->add($f);

    /** @var InputfieldInteger $f */
    $f = $modules->get('InputfieldInteger');
    $f->label = $this->_('Option column quantity');
    $f->description = 
      $this->_('If you want the options to display in columns, enter the number of columns you want to use (up to 10).') . ' ' . 
      $this->_('To display options inline side-by-side enter the number `1`.');
    $f->notes = $this->_('Note that the “option column width” setting is preferable for responsive behavior.'); 
    $f->attr('name', 'optionColumns'); 
    $f->attr('value', (int) $this->optionColumns); 
    if(!$this->optionColumns) $f->collapsed = Inputfield::collapsedYes;
    $inputfields->add($f);

    return $inputfields; 
  }

}