<?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;
	}
}
