889 lines
25 KiB
Text
889 lines
25 KiB
Text
<?php namespace ProcessWire;
|
||
|
||
/**
|
||
* Toggle Inputfield
|
||
*
|
||
* An Inputfield for handling an on/off toggle that maintains a boolean value (or null when no selection).
|
||
* This provides an alternative to a single checkbox field.
|
||
*
|
||
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
|
||
* https://processwire.com
|
||
*
|
||
* API usage example
|
||
* ~~~~~~
|
||
* // Default behavior displays toggle of "Yes" and "No":
|
||
* $f = $modules->get('InputfieldToggle');
|
||
* $f->attr('name', 'test_toggle_field');
|
||
* $f->label = 'Do you like toggle fields?';
|
||
*
|
||
* // Optionally make it show "On" and "Off" (rather than "Yes" and "No"):
|
||
* $f->labelType = InputfieldToggle::labelTypeOn;
|
||
*
|
||
* // Optionally set custom labels:
|
||
* $f->labelType = InputfieldToggle::labelTypeCustom;
|
||
* $f->yesLabel = 'Yes please';
|
||
* $f->noLabel = 'No thanks';
|
||
*
|
||
* // Optionally add an "other" option with label "Not sure":
|
||
* $f->useOther = true;
|
||
* $f->otherLabel = 'Not sure';
|
||
*
|
||
* // Set the value: 0=No, 1=Yes, 2=Other, or blank string '' for no selection (Unknown)
|
||
* $f->val(1);
|
||
*
|
||
* // Optionally set to use radio buttons rather than toggle buttons:
|
||
* $f->inputfieldClass = 'InputfieldRadios';
|
||
*
|
||
* // Remember to add to your InputfieldForm, InputfieldWrapper or InputfieldFieldset:
|
||
* $form->add($f);
|
||
* echo $form->render();
|
||
* ~~~~~~
|
||
*
|
||
* @property int|string $value Integer value when selection is made or blank string when no selection (0=No, 1=Yes, 2=Other, ''=Unknown)
|
||
* @property int $labelType Label type to use, see the labelType constants (default=labelTypeYes)
|
||
* @property int $valueType Type of value for methods that ask for it (use one of the valueType constants)
|
||
* @property string $yesLabel Custom yes/on label
|
||
* @property string $noLabel Custom no/off label
|
||
* @property string $otherLabel Custom label for optional other value Label to use for "other" option
|
||
* @property int|bool $useReverse Reverse the order of the Yes/No options? (default=false)
|
||
* @property int|bool $useOther Use the "other" option? (default=false)
|
||
* @property bool|int $useVertical Use vertically oriented radio buttons? Applies only if $inputfieldClass is 'InputfieldRadios' (default=false)
|
||
* @property bool|int $useDeselect Allow radios or toggles to be de-selected, enabling possibility of no-selection? (default=false)
|
||
* @property string $defaultOption Default selected value of 'no', 'yes', 'other' or 'none' (default='none')
|
||
* @property string $inputfieldClass Inputfield class to use or blank for this toggle buttons (default='')
|
||
*
|
||
* @method InputfieldSelect|InputfieldRadios getInputfield()
|
||
*
|
||
*/
|
||
class InputfieldToggle extends Inputfield {
|
||
|
||
public static function getModuleInfo() {
|
||
return array(
|
||
'title' => __('Toggle', __FILE__),
|
||
'summary' => __('A toggle providing similar input capability to a checkbox but much more configurable.', __FILE__),
|
||
'version' => 1,
|
||
);
|
||
}
|
||
|
||
// label type constants
|
||
const labelTypeYes = 0;
|
||
const labelTypeTrue = 1;
|
||
const labelTypeOn = 2;
|
||
const labelTypeEnabled = 3;
|
||
const labelTypeCustom = 100;
|
||
|
||
// value constants
|
||
const valueNo = 0;
|
||
const valueYes = 1;
|
||
const valueOther = 2;
|
||
const valueUnknown = '';
|
||
|
||
/**
|
||
* Array of all label types
|
||
*
|
||
* @var array
|
||
*
|
||
*/
|
||
protected $labelTypes = array(
|
||
'yes' => self::labelTypeYes,
|
||
'true' => self::labelTypeOn,
|
||
'on' => self::labelTypeTrue,
|
||
'enabled' => self::labelTypeEnabled,
|
||
'custom' => self::labelTypeCustom,
|
||
);
|
||
|
||
/**
|
||
* Array of all value types
|
||
*
|
||
* @var array
|
||
*
|
||
*/
|
||
protected $valueTypes = array(
|
||
'no' => self::valueNo,
|
||
'yes' => self::valueYes,
|
||
'other' => self::valueOther,
|
||
'unknown' => self::valueUnknown
|
||
);
|
||
|
||
/**
|
||
* Deleted Inputfield object for rendering (InputfieldRadios, InputfieldSelect, etc.)
|
||
*
|
||
* @var InputfieldSelect|InputfieldRadios Or any that extends them and does not have array value
|
||
*
|
||
*/
|
||
protected $inputfield = null;
|
||
|
||
/**
|
||
* Cached result of a getAllLabels() call
|
||
*
|
||
* @var array
|
||
*
|
||
*/
|
||
protected $allLabels = array();
|
||
|
||
/**
|
||
* Manually added custom options of [ value => label ]
|
||
*
|
||
* @var array
|
||
*
|
||
*/
|
||
protected $customOptions = array();
|
||
|
||
/**
|
||
* Construct and set default settings
|
||
*
|
||
*/
|
||
public function __construct() {
|
||
|
||
$this->set('labelType', self::labelTypeYes);
|
||
$this->set('yesLabel', '✓');
|
||
$this->set('noLabel', '✗');
|
||
$this->set('otherLabel', $this->_('?'));
|
||
$this->set('useOther', 0);
|
||
$this->set('useReverse', 0);
|
||
$this->set('useVertical', 0);
|
||
$this->set('useDeselect', 0);
|
||
$this->set('defaultOption', 'none');
|
||
$this->set('inputfieldClass', '0');
|
||
|
||
$this->set('settings', array(
|
||
'inputCheckedClass' => '',
|
||
'labelCheckedClass' => '',
|
||
));
|
||
|
||
$this->attr('value', self::valueUnknown);
|
||
|
||
parent::__construct();
|
||
}
|
||
|
||
public function wired() {
|
||
$languages = $this->wire('languages');
|
||
if($languages) {
|
||
foreach($languages as $language) {
|
||
if($language->isDefault()) continue;
|
||
$this->set("yesLabel$language", '');
|
||
$this->set("noLabel$language", '');
|
||
$this->set("otherLabel$language", '');
|
||
}
|
||
}
|
||
parent::wired();
|
||
}
|
||
|
||
/**
|
||
* Is the current value empty? (i.e. no selection)
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function isEmpty() {
|
||
$value = $this->val();
|
||
if($value === self::valueUnknown) return true;
|
||
if(is_int($value)) {
|
||
if($this->hasCustomOptions()) {
|
||
if(isset($this->customOptions[$value])) return false;
|
||
} else {
|
||
if($value > -1) return false;
|
||
}
|
||
} else if($value && $value !== 'unknown' && isset($this->valueTypes[$value])) {
|
||
return false;
|
||
}
|
||
if($value === self::valueOther && $this->useOther) return false;
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Sanitize the value to be one ofthe constants: valueYes, valueNo, valueOther, valueUnknown
|
||
*
|
||
* @param string|int $value Value to sanitize
|
||
* @param bool $getName Get internal name of value rather than value? (default=false)
|
||
* @return int|string
|
||
*
|
||
*/
|
||
public function sanitizeValue($value, $getName = false) {
|
||
|
||
if($value === null) {
|
||
return $getName ? 'unknown' : self::valueUnknown;
|
||
}
|
||
|
||
if(is_bool($value)) {
|
||
if($getName) return $value ? 'yes' : 'no';
|
||
return $value ? self::valueYes : self::valueNo;
|
||
}
|
||
|
||
$intValue = strlen("$value") && ctype_digit("$value") ? (int) "$value" : '';
|
||
$strValue = strtolower("$value");
|
||
|
||
if($this->hasCustomOptions()) {
|
||
if($intValue !== '') $value = $intValue;
|
||
$value = isset($this->customOptions[$value]) ? $value : self::valueUnknown;
|
||
|
||
} else if($intValue === self::valueNo || $intValue === self::valueYes) {
|
||
$value = $intValue;
|
||
|
||
} else if($intValue === self::valueOther) {
|
||
$value = $intValue;
|
||
|
||
} else if($strValue === 'yes' || $strValue === 'on' || $strValue === 'true') {
|
||
$value = self::valueYes;
|
||
|
||
} else if($strValue === 'no' || $strValue === 'off' || $strValue === 'false') {
|
||
$value = self::valueNo;
|
||
|
||
} else if($strValue === 'unknown' || $strValue === '') {
|
||
$value = self::valueUnknown;
|
||
|
||
} else if(is_string($value) && strlen($value)) {
|
||
// attempt to match to a label
|
||
$value = null;
|
||
foreach($this->getAllLabels() as $key => $label) {
|
||
if(strtolower($label) !== $strValue) continue;
|
||
list($labelType, $valueType, $languageName) = explode(':', $key);
|
||
if($labelType || $languageName) {} // ignore
|
||
$value = $this->valueTypes[$valueType];
|
||
break;
|
||
}
|
||
if($value === null) $value = self::valueUnknown;
|
||
|
||
} else {
|
||
$value = self::valueUnknown; // blank string
|
||
}
|
||
|
||
if($getName && !$this->hasCustomOptions()) {
|
||
if($value === self::valueUnknown) {
|
||
$value = 'unknown';
|
||
} else if($value === self::valueYes) {
|
||
$value = 'yes';
|
||
} else if($value === self::valueNo) {
|
||
$value = 'no';
|
||
} else if($value === self::valueOther) {
|
||
$value = 'other';
|
||
}
|
||
}
|
||
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Set attribute
|
||
*
|
||
* @param array|string $key
|
||
* @param array|bool|int|string $value
|
||
* @return Inputfield
|
||
*
|
||
*/
|
||
public function setAttribute($key, $value) {
|
||
if($key === 'value') $value = $this->sanitizeValue($value);
|
||
return parent::setAttribute($key, $value);
|
||
}
|
||
|
||
/**
|
||
* Get the delegated Inputfield that will be used for rendering selectable options
|
||
*
|
||
* @return InputfieldRadios|InputfieldSelect|InputfieldToggle
|
||
*
|
||
*/
|
||
public function ___getInputfield() {
|
||
|
||
if($this->inputfield) return $this->inputfield;
|
||
|
||
$class = $this->getSetting('inputfieldClass');
|
||
if(empty($class) || $class === $this->className()) {
|
||
if(false && $this->wire('adminTheme') == 'AdminThemeDefault') {
|
||
// clicking toggles jumps to top of page on AdminThemeDefault for some reason
|
||
// even if JS click events are canceled, so use radios instead
|
||
$class = 'InputfieldRadios';
|
||
} else {
|
||
return $this;
|
||
}
|
||
}
|
||
|
||
$f = $this->wire('modules')->get($class);
|
||
if(!$f || $f === $this) return $this;
|
||
|
||
$this->addClass($class, 'wrapClass');
|
||
|
||
/** @var InputfieldSelect|InputfieldRadios $f */
|
||
$f->attr('name', $this->attr('name'));
|
||
$f->attr('id', $this->attr('id'));
|
||
$f->addClass($this->attr('class'));
|
||
|
||
if(!$this->useVertical) {
|
||
$f->set('optionColumns', 1);
|
||
}
|
||
|
||
$val = $this->val();
|
||
$options = $this->getOptions();
|
||
$f->addOptions($options);
|
||
|
||
if(isset($options[$val]) && method_exists($f, 'addOptionAttributes')) {
|
||
$f->addOptionAttributes($val, array('input.class' => 'InputfieldToggleChecked'));
|
||
}
|
||
|
||
$f->val($val);
|
||
|
||
$this->inputfield = $f;
|
||
|
||
return $f;
|
||
}
|
||
|
||
/**
|
||
* Render ready
|
||
*
|
||
* @param Inputfield|null $parent
|
||
* @param bool $renderValueMode
|
||
* @return bool
|
||
*
|
||
*/
|
||
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
||
$f = $this->getInputfield();
|
||
if($f && $f !== $this) $f->renderReady($parent, $renderValueMode);
|
||
if($this->useDeselect && $this->defaultOption === 'none') {
|
||
$this->addClass('InputfieldToggleUseDeselect', 'wrapClass');
|
||
}
|
||
return parent::renderReady($parent, $renderValueMode);
|
||
}
|
||
|
||
/**
|
||
* Render value
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function ___renderValue() {
|
||
$label = $this->getValueLabel($this->attr('value'));
|
||
$value = $this->formatLabel($label, true);
|
||
return $value;
|
||
}
|
||
|
||
/**
|
||
* Render input element(s)
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
public function ___render() {
|
||
|
||
$value = $this->val();
|
||
$default = $this->getSetting('defaultOption');
|
||
|
||
// check if we should assign a default value
|
||
if($default && ("$value" === self::valueUnknown || !strlen("$value"))) {
|
||
if($default === 'yes') {
|
||
$this->val(self::valueYes);
|
||
} else if($default === 'no') {
|
||
$this->val(self::valueNo);
|
||
} else if($default === 'other' && $this->useOther) {
|
||
$this->val(self::valueOther);
|
||
}
|
||
}
|
||
|
||
$f = $this->getInputfield();
|
||
|
||
if($f && $f !== $this) {
|
||
$f->val($this->val());
|
||
$out = $f->render();
|
||
} else {
|
||
$out = $this->renderToggle();
|
||
}
|
||
|
||
// hidden input to indicate presence when no selection is made (like with radios)
|
||
|
||
/** @var InputfieldButton $btn */
|
||
$button = $this->wire('modules')->get('InputfieldButton');
|
||
$button->setSecondary(true);
|
||
$button->val('1');
|
||
$button->removeAttr('name');
|
||
|
||
$input = $this->wire('modules')->get('InputfieldText');
|
||
$input->attr('name', "_{$this->name}_");
|
||
$input->val(1);
|
||
|
||
$out .= "<div class='InputfieldToggleHelper'>" . $input->render() . $button->render() . "</div>";
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* Render default input toggles
|
||
*
|
||
* @return string
|
||
*
|
||
*/
|
||
protected function renderToggle() {
|
||
|
||
$id = $this->attr('id');
|
||
$name = $this->attr('name');
|
||
$checkedValue = $this->val();
|
||
$out = '';
|
||
|
||
foreach($this->getOptions() as $value => $label) {
|
||
$checked = "$checkedValue" === "$value" ? "checked " : "";
|
||
$inputClass = $checked ? 'InputfieldToggleChecked' : '';
|
||
$labelClass = $checked ? 'InputfieldToggleCurrent' : '';
|
||
$label = $this->formatLabel($label);
|
||
$out .=
|
||
"<input type='radio' id='{$id}_$value' name='$name' class='$inputClass' value='$value' $checked/>" .
|
||
"<label for='{$id}_$value' class='$labelClass'><span class='pw-no-select'>$label</span></label>";
|
||
}
|
||
|
||
return
|
||
"<div class='pw-clearfix ui-helper-clearfix'>" .
|
||
"<div class='InputfieldToggleGroup'>$out</div>" .
|
||
"</div>";
|
||
}
|
||
|
||
/**
|
||
* Process input
|
||
*
|
||
* @param WireInputData $input
|
||
* @return $this
|
||
*
|
||
*/
|
||
public function ___processInput(WireInputData $input) {
|
||
|
||
$prevValue = $this->val();
|
||
$value = $input[$this->name];
|
||
$intValue = strlen($value) && ctype_digit("$value") ? (int) $value : null;
|
||
|
||
if($value === null && $input["_{$this->name}_"] === null) {
|
||
// input was not rendered in the submitted post, so should be ignored
|
||
|
||
} else if($this->hasCustomOptions()) {
|
||
// custom options
|
||
if(isset($this->customOptions[$value])) $this->val($value);
|
||
|
||
} else if($intValue === self::valueYes || $intValue === self::valueNo) {
|
||
// yes or no selected
|
||
$this->val($intValue);
|
||
|
||
} else if($intValue === self::valueOther && $this->useOther) {
|
||
// other selected
|
||
$this->val($intValue);
|
||
|
||
} else if($value === self::valueUnknown || $value === null) {
|
||
// no selection
|
||
$this->val(self::valueUnknown);
|
||
|
||
} else {
|
||
// something we don't recognize
|
||
}
|
||
|
||
if($this->val() !== $prevValue) {
|
||
$this->trackChange('value', $prevValue, $this->val());
|
||
}
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Get labels for the given label type
|
||
*
|
||
* @param int $labelType Specify toggle type constant or omit to use configured toggle type.
|
||
* @param Language|int|string|null Language or omit to use current user’s language. (default=null)
|
||
* @return array Returned array has these indexes:
|
||
* `no` (string): No/Off state label
|
||
* `yes` (string): Yes/On state label
|
||
* `other` (string): Other state label
|
||
* `unknown` (string): No selection label
|
||
*
|
||
*/
|
||
public function getLabels($labelType = null, $language = null) {
|
||
|
||
if($labelType === null) $labelType = $this->labelType;
|
||
|
||
/** @var Languages $langauges */
|
||
$languages = $this->wire('languages');
|
||
$setLanguage = false;
|
||
$languageId = '';
|
||
$yes = '';
|
||
$no = '';
|
||
|
||
if($languages) {
|
||
/** @var User $user */
|
||
$user = $this->wire('user');
|
||
if(empty($language)) {
|
||
// use current user language
|
||
$language = $user->language;
|
||
} else if(is_int($language) || is_string($language)) {
|
||
// get language from specified language ID or name
|
||
$language = $languages->get($language);
|
||
}
|
||
if($language instanceof Page && $language->id != $user->language->id) {
|
||
// use other specified language
|
||
$languages->setLanguage($language);
|
||
$setLanguage = true;
|
||
} else {
|
||
// use current user language
|
||
$language = $user->language;
|
||
}
|
||
$languageId = $language && !$language->isDefault() ? $language->id : '';
|
||
}
|
||
|
||
switch($labelType) {
|
||
case self::labelTypeTrue:
|
||
$yes = $this->_('True');
|
||
$no = $this->_('False');
|
||
break;
|
||
case self::labelTypeOn:
|
||
$yes = $this->_('On');
|
||
$no = $this->_('Off');
|
||
break;
|
||
case self::labelTypeEnabled:
|
||
$yes = $this->_('Enabled');
|
||
$no = $this->_('Disabled');
|
||
break;
|
||
case self::labelTypeCustom:
|
||
$yes = $languageId ? $this->get("yesLabel$languageId|yesLabel") : $this->yesLabel;
|
||
$no = $languageId ? $this->get("noLabel$languageId|noLabel") : $this->noLabel;
|
||
break;
|
||
}
|
||
|
||
// default (labelTypeYes)
|
||
if(!strlen($yes)) $yes = $this->_('Yes');
|
||
if(!strlen($no)) $no = $this->_('No');
|
||
|
||
// other and unknown labels
|
||
$other = $languageId ? $this->get("otherLabel$languageId|otherLabel") : $this->otherLabel;
|
||
if(empty($other)) $other = $this->_('Other');
|
||
$unknown = $this->_('Unknown');
|
||
|
||
if($setLanguage && $languages) $languages->unsetLanguage();
|
||
|
||
return array(
|
||
'no' => $no,
|
||
'yes' => $yes,
|
||
'other' => $other,
|
||
'unknown' => $unknown
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Get all possible labels for all label types and all languages
|
||
*
|
||
* Returned array of labels (strings) indexed by "labelTypeNum:valueTypeName:languageName"
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function getAllLabels() {
|
||
|
||
if(!empty($this->allLabels)) return $this->allLabels;
|
||
|
||
/** @var Languages|null $languages */
|
||
$languages = $this->wire('languages');
|
||
|
||
$all = array();
|
||
|
||
foreach($this->labelTypes as $labelType) {
|
||
if($languages) {
|
||
foreach($languages as $language) {
|
||
foreach($this->getLabels($labelType, $language) as $valueType => $label) {
|
||
$all["$labelType:$valueType:$language->name"] = $label;
|
||
}
|
||
}
|
||
} else {
|
||
foreach($this->getLabels($labelType) as $valueType => $label) {
|
||
$all["$labelType:$valueType:default"] = $label;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $all;
|
||
}
|
||
|
||
/**
|
||
* Get the label for the currently set (or given) value
|
||
*
|
||
* @param bool|int|string|null $value Optionally provide value or omit to use currently set value attribute.
|
||
* @param int|null $labelType Specify labelType constant or omit for selected label type.
|
||
* @param Language|int|string $language
|
||
* @return string Label string
|
||
*
|
||
*/
|
||
public function getValueLabel($value = null, $labelType = null, $language = null) {
|
||
|
||
if($value === null) $value = $this->val();
|
||
|
||
if($this->hasCustomOptions()) {
|
||
// get custom defined option label from addOption() call (API usage only)
|
||
return isset($this->customOptions[$value]) ? $this->customOptions[$value] : self::valueUnknown;
|
||
}
|
||
|
||
$labels = $this->getLabels($labelType, $language);
|
||
|
||
if($value === null || $value === self::valueUnknown) return $labels['unknown'];
|
||
if(is_bool($value)) return $value ? $labels['yes'] : $labels['no'];
|
||
|
||
if($value === self::valueOther) return $labels['other'];
|
||
if($value === self::valueYes) return $labels['yes'];
|
||
if($value === self::valueNo) return $labels['no'];
|
||
|
||
return $labels['unknown'];
|
||
}
|
||
|
||
/**
|
||
* Format label for HTML output (entity encode, etc.)
|
||
*
|
||
* @param string $label
|
||
* @param bool $allowIcon Allow icon markup to appear in label?
|
||
* @return string
|
||
*
|
||
*/
|
||
public function formatLabel($label, $allowIcon = true) {
|
||
$label = $this->wire('sanitizer')->entities1($label);
|
||
if(strpos($label, 'icon-') !== false && preg_match('/\bicon-([-_a-z0-9]+)/', $label, $matches)) {
|
||
$name = $matches[1];
|
||
$icon = $allowIcon ? $icon = wireIconMarkup($name, 'fw') : '';
|
||
$label = str_replace("icon-$name", $icon, $label);
|
||
}
|
||
return trim($label);
|
||
}
|
||
|
||
/**
|
||
* Get all selectable options as array of [ value => label ]
|
||
*
|
||
* @return array
|
||
*
|
||
*/
|
||
public function getOptions() {
|
||
// use custom options instead if any have been set from an addOption() call
|
||
if($this->hasCustomOptions()) return $this->customOptions;
|
||
// use built in toggle options
|
||
$options = array();
|
||
$values = $this->useReverse ? array(self::valueNo, self::valueYes) : array(self::valueYes, self::valueNo);
|
||
if($this->useOther) $values[] = self::valueOther;
|
||
foreach($values as $value) {
|
||
$options[$value] = $this->getValueLabel($value);
|
||
}
|
||
return $options;
|
||
}
|
||
|
||
/**
|
||
* Add a selectable option (custom API usage only, overrides built-in options)
|
||
*
|
||
* Note that once you use this, your options take over and Toggle's default yes/no/other
|
||
* are no longer applicable. This is for custom API use and is not used by FieldtypeToggle.
|
||
*
|
||
* @param int|string $value
|
||
* @param null|string $label
|
||
* @return $this
|
||
* @throws WireException if you attempt to call this method when used with FieldtypeToggle
|
||
*
|
||
*/
|
||
public function addOption($value, $label = null) {
|
||
if($this->hasFieldtype) {
|
||
throw new WireException('The addOption() method is not available for FieldtypeToggle');
|
||
}
|
||
if($label === null) $label = $value;
|
||
$this->customOptions[$value] = $label;
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Set all options with array of [ value => label ] (custom API usage only, overrides built-in options)
|
||
*
|
||
* Once you use this (with a non-empty array, your set options take over and the
|
||
* built-in yes/no/other no longer apply. This is for custom API use and is not used
|
||
* by FieldtypeToggle.
|
||
*
|
||
* The value for each option must be an integer value between -128 and 127.
|
||
*
|
||
* @param array $options
|
||
* @return $this
|
||
* @throws WireException if you attempt to call this method when used with FieldtypeToggle
|
||
*
|
||
*/
|
||
public function setOptions(array $options) {
|
||
$this->customOptions = array();
|
||
foreach($options as $key => $value) {
|
||
$this->addOption($key, $value);
|
||
}
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Are custom options in use?
|
||
*
|
||
* @return bool
|
||
*
|
||
*/
|
||
protected function hasCustomOptions() {
|
||
return count($this->customOptions) > 0;
|
||
}
|
||
|
||
/**
|
||
* Return a list of config property names allowed for fieldgroup/template context
|
||
*
|
||
* @param Field $field
|
||
* @return array of Inputfield names
|
||
* @see Fieldtype::getConfigAllowContext()
|
||
*
|
||
*/
|
||
public function ___getConfigAllowContext($field) {
|
||
return array_merge(parent::___getConfigAllowContext($field), array(
|
||
'labelType',
|
||
'inputfieldClass',
|
||
'yesLabel',
|
||
'noLabel',
|
||
'otherLabel',
|
||
'useVertical',
|
||
'useDeselect',
|
||
'useOther',
|
||
'useReverse',
|
||
'defaultOption',
|
||
));
|
||
}
|
||
|
||
/**
|
||
* Configure Inputfield
|
||
*
|
||
* @return InputfieldWrapper
|
||
*
|
||
*/
|
||
public function ___getConfigInputfields() {
|
||
|
||
/** @var Modules $modules */
|
||
$modules = $this->wire('modules');
|
||
/** @var Languages $languages */
|
||
$languages = $this->wire('languages');
|
||
$inputfields = parent::___getConfigInputfields();
|
||
|
||
if($this->hasFieldtype) {
|
||
/** @var InputfieldFieldset $fieldset */
|
||
$fieldset = $modules->get('InputfieldFieldset');
|
||
$fieldset->label = $this->_('Toggle field labels and input settings');
|
||
$fieldset->icon = 'toggle-on';
|
||
$inputfields->prepend($fieldset);
|
||
} else {
|
||
$fieldset = $inputfields;
|
||
}
|
||
|
||
$removals = array('defaultValue');
|
||
foreach($removals as $name) {
|
||
$f = $inputfields->getChildByName($name);
|
||
if($f) $inputfields->remove($f);
|
||
}
|
||
|
||
/** @var InputfieldRadios $f */
|
||
$f = $modules->get('InputfieldRadios');
|
||
$f->attr('name', 'labelType');
|
||
$f->label = $this->_('Label type');
|
||
foreach($this->labelTypes as $labelType) {
|
||
if($labelType == self::labelTypeCustom) {
|
||
$label = $this->_('Custom');
|
||
} else {
|
||
$label = $this->getValueLabel(self::valueYes, $labelType) . '/' . $this->getValueLabel(self::valueNo, $labelType);
|
||
}
|
||
$f->addOption($labelType, $label);
|
||
}
|
||
$f->attr('value', (int) $this->labelType);
|
||
$f->columnWidth = 34;
|
||
$fieldset->add($f);
|
||
|
||
/** @var InputfieldRadios $f */
|
||
$f = $modules->get('InputfieldRadios');
|
||
$f->attr('name', 'inputfieldClass');
|
||
$f->label = $this->_('Input type');
|
||
$f->addOption('0', $this->_('Toggle buttons'));
|
||
foreach($modules->findByPrefix('Inputfield') as $name) {
|
||
if(!wireInstanceOf($name, 'InputfieldSelect')) continue;
|
||
if(wireInstanceOf($name, 'InputfieldHasArrayValue')) continue;
|
||
$label = str_replace('Inputfield', '', $name);
|
||
$f->addOption($name, $label);
|
||
}
|
||
$f->val($this->getSetting('inputfieldClass'));
|
||
$f->columnWidth = 33;
|
||
$fieldset->add($f);
|
||
|
||
/** @var InputfieldRadios $f */
|
||
$f = $modules->get('InputfieldRadios');
|
||
$f->attr('name', 'useVertical');
|
||
$f->label = $this->_('Radios');
|
||
$f->addOption(0, $this->_('Horizontal'));
|
||
$f->addOption(1, $this->_('Vertical'));
|
||
$f->val($this->useVertical ? 1 : 0);
|
||
$f->columnWidth = 33;
|
||
$f->showIf = 'inputfieldClass=InputfieldRadios';
|
||
$fieldset->add($f);
|
||
|
||
/** @var InputfieldCheckbox $f */
|
||
$f = $modules->get('InputfieldCheckbox');
|
||
$f->attr('name', 'useOther');
|
||
$f->label = $this->_('Use a 3rd “other” option?');
|
||
if($this->useOther) $f->attr('checked', 'checked');
|
||
$f->columnWidth = 34;
|
||
$fieldset->add($f);
|
||
|
||
/** @var InputfieldCheckbox $f */
|
||
$f = $modules->get('InputfieldCheckbox');
|
||
$f->attr('name', 'useReverse');
|
||
$f->label = $this->_('Reverse order of yes/no options?');
|
||
if($this->useReverse) $f->attr('checked', 'checked');
|
||
$f->columnWidth = 33;
|
||
$fieldset->add($f);
|
||
|
||
/** @var InputfieldCheckbox $f */
|
||
$f = $modules->get('InputfieldCheckbox');
|
||
$f->attr('name', 'useDeselect');
|
||
$f->label = $this->_('Support click to de-select?');
|
||
$f->showIf = "inputfieldClass=0|InputfieldRadios";
|
||
if($this->useDeselect) $f->attr('checked', 'checked');
|
||
$f->columnWidth = 33;
|
||
$f->showIf = 'defaultOption=none';
|
||
$fieldset->add($f);
|
||
|
||
$customStates = array(
|
||
'yesLabel' => $this->_('yes/on'),
|
||
'noLabel' => $this->_('no/off'),
|
||
);
|
||
|
||
$labelFor = $this->_('Label for “%s” option');
|
||
|
||
/** @var InputfieldText $f */
|
||
foreach($customStates as $name => $label) {
|
||
$f = $modules->get('InputfieldText');
|
||
$f->attr('name', $name);
|
||
$f->label = sprintf($labelFor, $label);
|
||
$f->showIf = 'labelType=' . self::labelTypeCustom;
|
||
$f->val($this->get($name));
|
||
$f->columnWidth = 50;
|
||
if($languages) {
|
||
$f->useLanguages = true;
|
||
foreach($languages as $language) {
|
||
$langValue = $this->get("$name$language");
|
||
if(!$language->isDefault()) $f->set("value$language", $langValue);
|
||
}
|
||
}
|
||
$fieldset->add($f);
|
||
}
|
||
|
||
/** @var InputfieldText $f */
|
||
$f = $modules->get('InputfieldText');
|
||
$f->attr('name', 'otherLabel');
|
||
$f->label = sprintf($labelFor, $this->_('other'));
|
||
$f->showIf = 'useOther=1';
|
||
$f->val($this->get('otherLabel'));
|
||
if($languages) {
|
||
$f->useLanguages = true;
|
||
foreach($languages as $language) {
|
||
if(!$language->isDefault()) $f->set("value$language", $this->get("otherLabel$language"));
|
||
}
|
||
}
|
||
$fieldset->add($f);
|
||
|
||
/** @var InputfieldToggle $f */
|
||
$f = $modules->get('InputfieldToggle');
|
||
$f->set('inputfieldClass', $this->inputfieldClass);
|
||
$f->attr('name', 'defaultOption');
|
||
$f->label = $this->_('Default selected option');
|
||
$f->addOption('yes', $this->getValueLabel(self::valueYes));
|
||
$f->addOption('no', $this->getValueLabel(self::valueNo));
|
||
if($this->useOther) $f->addOption('other', $this->getValueLabel(self::valueOther));
|
||
$f->addOption('none', $this->_('No selection'));
|
||
$f->val($this->defaultOption);
|
||
$f->addClass('InputfieldToggle', 'wrapClass');
|
||
$fieldset->add($f);
|
||
|
||
return $inputfields;
|
||
}
|
||
}
|