Subversion Repositories web.active

Rev

Rev 1 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download

<?php namespace ProcessWire;

/**
 * AsmSelect is a sortable multiple-select Inputfield
 * 
 * Copyright (c) 2009-2022 by Ryan Cramer
 * 
 * @property bool $addable Can items be added to selection? (default=true)
 * @property string $addItemTarget Where to place new selected items in list: top or bottom (default='bottom')
 * @property bool $animate Animate the the adding/removing of items in the list? (default=false)
 * @property bool $debugMode Debug mode keeps original select visible (default=false)
 * @property bool $deletable Can items be removed from selection? (default=true)
 * @property float|string $deletedOpacity Opacity of deleted item, set to 1.0 to disable opacity adjustment (applicable only if hideDeleted=true)
 * @property string $deletedPrepend Deleted item values are prepended with this character in the form submission (applicable only if hideDeleted=true) (default='-')
 * @property string $editLabel Text used in the "edit" link (if editLink is populated)
 * @property string $editLink Optional URL options can link to with tag {value} replaced by option value, i.e. /path/to/page/edit?id={$value}
 * @property string $editLinkButtonSelector Button selector for finding buttons that should become modal window buttons
 * @property string|bool $editLinkModal Whether the edit link (if used) should be modal or "longclick" for longclick modal only (default=true)
 * @property bool $editLinkOnlySelected When true, edit link only appears for items that were already selected (default=true)
 * @property bool $fieldset Use fieldset support? (for PW Fieldset types) (default=false)
 * @property bool $hideDeleted Hide items when deleted. If false, items remain but are marked for deletion (default=true)
 * @property bool $hideWhenEmpty Hide the <select> when there are no items available to select? (default=false)
 * @property bool $highlight Use the highlight feature?  (default=false)
 * @property string $highlightAddedLabel Text that precedes highlight of added item (default='Added: ')
 * @property string $highlightRemovedLabel Text that precedes highlight of removed item (default='Removed: ')
 * @property string $removeLabel Text used in the "remove" link
 * @property bool $sortable Should the list be sortable? (default=true)
 * @property string $sortLabel Sortable handle/icon
 * @property int|bool $usePageEdit Use page editor links for selected Page items, when user has edit permission? (default=false)
 *
 */
class InputfieldAsmSelect extends InputfieldSelectMultiple implements InputfieldHasArrayValue, InputfieldHasSortableValue {
  
  /**
   * Module info
   *
   * @return array
   *
   */
  public static function getModuleInfo() {
    return array(
      'title' => __('asmSelect', __FILE__),
      'version' => 203,
      'summary' => __('Multiple selection, progressive enhancement to select multiple', __FILE__), // Module Summary
      'permanent' => true,
    );
  }

  /**
   * Custom defined AsmSelect options
   * 
   * @var array
   * 
   */
  protected $asmOptions = array();

  /**
   * Options as specified at init() state (common to all instances)
   * 
   * @var array
   * 
   */
  protected $asmDefaults = array();

  /**
   * Common option names for asmSelect
   * 
   * (note this array gets flipped in the constructor)
   * 
   * @var array
   * 
   */
  protected $asmOptionNames = array(
    // general
    'addable',
    'addItemTarget',
    'animate', 
    'debugMode',
    'deletable',
    'deletedOpacity',
    'deletedPrepend',
    'fieldset',
    'hideDeleted',
    'hideWhenEmpty',
    'highlight', 
    'jQueryUI',
    'sortable', 
    
    // labels
    'editLabel',
    'highlightAddedLabel',
    'highlightRemovedLabel',
    'removeLabel',
    'sortLabel',
    
    // edit link
    'editLink',
    'editLinkButtonSelector',
    'editLinkModal',
    'editLinkOnlySelected',
  );

  /**
   * Construct
   * 
   */
  public function __construct() {
    $this->set('usePageEdit', 0);
    $this->asmOptionNames = array_flip($this->asmOptionNames);
    parent::__construct();
  }

  /**
   * Init
   * 
   */
  public function init() {

    parent::init(); 
    
    $this->setAsmSelectOptions(array(
      'sortable' => true,
      'fieldset' => false,
      
      // an optional edit or detail link where items can be modified or viewed
      // i.e. /path/to/page/?id={value} where {value} is replaced with option value
      'editLink' => '',
      'editLabel' => "<i class='fa fa-fw fa-edit asmIcon'></i>",
      
      // only applicable if editLink is set. set to false if you don't want edit link to be modal
      'editLinkModal' => true,
    ));

    if($this->wire('adminTheme')) $this->setAsmSelectOptions(array(
      // replace jquery ui icon default with a font-awesome icon
      'removeLabel' => "<i class='fa fa-trash'></i>",
      
      // replace jquery ui icon default with a font-awesome icon
      'sortLabel' => "<i class='fa fa-fw fa-arrows'></i>",
    ));

    // cancel the 'size' attribute used by select multiple
    $this->set('size', null); 
    
    $this->wire()->config->js('InputfieldAsmSelect', $this->asmOptions);
    $this->asmDefaults = $this->asmOptions;
  }

  /**
   * Set Inputfield property or AsmSelect option
   * 
   * @param string $key
   * @param mixed $value
   * @return Inputfield|InputfieldAsmSelect|InputfieldSelect
   * 
   */
  public function set($key, $value) {
    if(isset($this->asmOptionNames[$key])) return $this->setAsmSelectOption($key, $value);
    return parent::set($key, $value);
  }

  /**
   * Set custom option for AsmSelect
   * 
   * @param string $key
   * @param string|bool $value
   * @return self
   * 
   */
  public function setAsmSelectOption($key, $value) {
    $this->asmOptions[$key] = $value; 
    return $this;
  }

  /**
   * Set multiple custom options for AsmSelect
   * 
   * @param array $options
   * @return self
   * @since 3.0.169
   * 
   */
  public function setAsmSelectOptions(array $options) {
    $this->asmOptions = array_merge($this->asmOptions, $options);
    return $this;
  }
  
  /**
   * Called before render()
   *
   * @param Inputfield $parent
   * @param bool $renderValueMode
   * @return bool
   *
   */
  public function renderReady(Inputfield $parent = null, $renderValueMode = false) {

    $modules = $this->wire()->modules;
    $config = $this->wire()->config;
    
    // asmSelect requires jQuery UI, so we enforce it being loaded here
    $modules->get('JqueryCore');
    
    /** @var JqueryUI $jQueryUI */
    $jQueryUI = $modules->get('JqueryUI'); 
    
    if(!empty($this->asmOptions['editLink'])) {
      $jQueryUI->use('modal');
    }

    if($this->hasFieldtype == 'FieldtypePage' && $this->usePageEdit && empty($this->asmOptions['editLink'])) {
      $this->setAsmSelectOptions(array(
        'editLink' => $config->urls->admin . 'page/edit/?id={value}',
        'editLinkOnlySelected' => false,
        'editLinkButtonSelector' => '.InputfieldSubmit button.ui-button:visible',
        'editLinkModal' => true,
      ));
    }

    // require javascript and css
    $class = $this->className();
    $info = self::getModuleInfo();
    $ver = $info['version'];
    $jsfile = $config->debug ? 'jquery.asmselect.js' : 'jquery.asmselect.min.js';
    $url = $config->urls($class);

    $config->scripts->add($url . "asmselect/$jsfile?v=$ver");
    $config->styles->add($url . "$class.css?v=$ver");
    $config->styles->add($url . "asmselect/jquery.asmselect.css?v=$ver");
    
    // $this->config->js($this->id, $this->asmOptions); // deprecated/legacy
    
    return parent::renderReady($parent, $renderValueMode);
  }

  /**
   * Render
   * 
   * @return string
   * 
   */
  public function ___render() {
  
    // compile settings unique to this instance into a JSON encoded data-asmopt attribute
    $settings = array();
    foreach($this->asmOptions as $key => $value) {
      if(!isset($this->asmDefaults[$key]) || $this->asmDefaults[$key] != $value) {
        $settings[$key] = $value;
      }
    }
    $this->attr('data-asmopt', json_encode($settings));

    // ensure selected options are placed as last in the AsmSelect select output
    $selectedOptions = $this->attr('value'); 
    foreach($selectedOptions as $id) {
      if(!isset($this->options[$id])) continue; 
      $label = $this->options[$id]; 
      unset($this->options[$id]);   
      $this->addOption($id, $label); 
    }

    return parent::___render();
  }

  /**
   * Field config
   * 
   * @return InputfieldWrapper
   * 
   */
  public function ___getConfigInputfields() {
    
    $inputfields = parent::___getConfigInputfields();
    if($this->hasFieldtype != 'FieldtypePage' || !$this->hasField) return $inputfields;
    
    /** @var InputfieldRadios $f */
    $f = $this->wire()->modules->get('InputfieldRadios');
    $f->attr('name', 'usePageEdit');
    $f->label = $this->_('Link selected pages to page editor?');
    $f->description = $this->_('When enabled, the selected label(s) will link to edit the selected page.');
    $f->addOption(0, $this->_('No'));
    $f->addOption(1, 
      $this->_('Yes') . ' ' . 
      $this->_('(in modal window)')
    );
    $f->attr('value', $this->usePageEdit);
    $f->optionColumns = 1;
    $f->collapsed = Inputfield::collapsedBlank;
    $inputfields->add($f);
    
    return $inputfields;
  }
}