349 lines
10 KiB
Text
349 lines
10 KiB
Text
|
<?php namespace ProcessWire;
|
||
|
|
||
|
/**
|
||
|
* An Inputfield for handling a single checkbox
|
||
|
*
|
||
|
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
|
||
|
* https://processwire.com
|
||
|
*
|
||
|
* Note: if you want a checkbox already checked, you need to add a setAttribute('checked', 'checked');
|
||
|
*
|
||
|
* @property string $checkedValue Value when checked (default=1)
|
||
|
* @property string $uncheckedValue Value when not checked (default='', blank string)
|
||
|
* @property string $label2 Alterate label to display next to checkbox (default=use regular label)
|
||
|
* @property string $checkboxLabel Same as label2, but used as part of field config rather than API-only config.
|
||
|
* @property bool $checkboxOnly Show only the checkbox without label text next to it? (default=false) @since 3.0.144
|
||
|
* @property array $labelAttrs Optional attributes for <label> element that surrounds checkbox (default=[]) @since 3.0.141
|
||
|
* @property int $autocheck When set to 1, setting value attribute to non-blank/non-zero automatically triggers checked.
|
||
|
*
|
||
|
*
|
||
|
*/
|
||
|
class InputfieldCheckbox extends Inputfield {
|
||
|
|
||
|
public static function getModuleInfo() {
|
||
|
return array(
|
||
|
'title' => __('Checkbox', __FILE__), // Module Title
|
||
|
'summary' => __('Single checkbox toggle', __FILE__), // Module Summary
|
||
|
'version' => 106,
|
||
|
'permanent' => true,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Default checked value
|
||
|
*
|
||
|
*/
|
||
|
const checkedValueDefault = 1;
|
||
|
|
||
|
/**
|
||
|
* Default unchecked value
|
||
|
*
|
||
|
*/
|
||
|
const uncheckedValueDefault = '';
|
||
|
|
||
|
/**
|
||
|
* True if the $checkedValue set manually (and should be used as a label), false if it was inherited from $value attribute
|
||
|
*
|
||
|
*/
|
||
|
protected $checkedValueIsLabel = false;
|
||
|
|
||
|
/**
|
||
|
* Construct and set default settings
|
||
|
*
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
|
||
|
$this->set('checkedValue', self::checkedValueDefault);
|
||
|
$this->checkedValueIsLabel = false; // cancel line above
|
||
|
$this->set('uncheckedValue', self::uncheckedValueDefault);
|
||
|
|
||
|
// when autocheck set to 1, setting the value attribute to non-zero automatically triggered checked=checked attribute
|
||
|
$this->set('autocheck', 0);
|
||
|
|
||
|
// alternate label for checkbox (both do the same thing but for different config context)
|
||
|
$this->set('label2', ''); // typically specified by API
|
||
|
$this->set('checkboxOnly', false); // render checkbox only, without showing label text
|
||
|
$this->set('checkboxLabel', ''); // typically specified by interactive config
|
||
|
$this->set('labelAttrs', array()); // Optional attributes for <label> element that surrounds checkbox (3.0.141+)
|
||
|
|
||
|
parent::__construct();
|
||
|
|
||
|
// Use an undefined skipLabel setting so we can detect when to apply a custom default in ___render()
|
||
|
// according to runtime conditions (placement after parent::__construct() is required)
|
||
|
$this->set('skipLabel', -1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Wired to ProcessWire instance
|
||
|
*
|
||
|
*/
|
||
|
public function wired() {
|
||
|
$languages = $this->wire()->languages;
|
||
|
if($languages) {
|
||
|
foreach($languages as $language) {
|
||
|
if(!$language->isDefault()) $this->set("checkboxLabel$language", "");
|
||
|
}
|
||
|
}
|
||
|
parent::wired();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Init
|
||
|
*
|
||
|
*/
|
||
|
public function init() {
|
||
|
parent::init();
|
||
|
$this->attr('checked', '');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render checkbox input
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function ___render() {
|
||
|
|
||
|
$user = $this->wire()->user;
|
||
|
|
||
|
// label placed to the right of the checkbox
|
||
|
$checkboxLabel = '';
|
||
|
|
||
|
if(!$this->checkboxOnly) {
|
||
|
// we will be displaying a label next to the checkbox
|
||
|
if($user->language) $checkboxLabel = $this->getSetting("checkboxLabel$user->language");
|
||
|
if(!$checkboxLabel) $checkboxLabel = $this->getSetting("checkboxLabel");
|
||
|
if(!$checkboxLabel && $this->checkedValueIsLabel) $checkboxLabel = $this->checkedValue;
|
||
|
if(!$checkboxLabel) $checkboxLabel = $this->getSetting('label2');
|
||
|
$checkboxLabel = trim($checkboxLabel);
|
||
|
}
|
||
|
|
||
|
if($this->getSetting('skipLabel') === -1) {
|
||
|
// nothing has modified the skipLabel setting so apply an automatic setting
|
||
|
if($checkboxLabel || $this->description) {
|
||
|
$this->set('skipLabel', Inputfield::skipLabelFor);
|
||
|
} else {
|
||
|
$this->set('skipLabel', Inputfield::skipLabelHeader);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!$checkboxLabel && !$this->checkboxOnly) {
|
||
|
$checkboxLabel = $this->label;
|
||
|
}
|
||
|
|
||
|
$checkboxAttrs = $this->getAttributes();
|
||
|
$checkboxAttrs['value'] = $this->checkedValue;
|
||
|
unset($checkboxAttrs['type']);
|
||
|
$checkboxAttrs = $this->getAttributesString($checkboxAttrs);
|
||
|
|
||
|
if(strlen($checkboxLabel)) {
|
||
|
if($this->getSetting('entityEncodeLabel') !== false) {
|
||
|
$checkboxLabel = $this->entityEncode($checkboxLabel, Inputfield::textFormatBasic);
|
||
|
}
|
||
|
$checkboxLabel = "<span class='pw-no-select'>$checkboxLabel</span>";
|
||
|
}
|
||
|
|
||
|
$labelAttrs = $this->getSetting('labelAttrs');
|
||
|
$labelAttrs = empty($labelAttrs) ? '' : ' ' . $this->getAttributesString($labelAttrs);
|
||
|
|
||
|
$out =
|
||
|
"<label$labelAttrs>" .
|
||
|
"<input type='checkbox' $checkboxAttrs />" .
|
||
|
$checkboxLabel .
|
||
|
"</label>";
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render value only
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function ___renderValue() {
|
||
|
$value = $this->val();
|
||
|
if($value != self::uncheckedValueDefault && $value != $this->uncheckedValue) {
|
||
|
$value = $this->wire()->sanitizer->entities($this->checkedValue);
|
||
|
$value = $value === "1" ? $this->_('Checked') : $value;
|
||
|
$value = wireIconMarkup('check-square-o') . " $value";
|
||
|
} else {
|
||
|
$value = $this->wire()->sanitizer->entities($this->uncheckedValue);
|
||
|
$value = empty($value) ? $this->_('Not checked') : $value;
|
||
|
$value = wireIconMarkup('square-o') . " $value";
|
||
|
}
|
||
|
return $value;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set attribute
|
||
|
*
|
||
|
* @param array|string $key
|
||
|
* @param array|int|string $value
|
||
|
* @return Inputfield|InputfieldCheckbox
|
||
|
*
|
||
|
*/
|
||
|
public function setAttribute($key, $value) {
|
||
|
|
||
|
if($key === 'value' && $value && "$value" !== "$this->uncheckedValue") {
|
||
|
if("$value" !== (string) self::checkedValueDefault) {
|
||
|
$this->checkedValue = $value;
|
||
|
$this->checkedValueIsLabel = false;
|
||
|
}
|
||
|
// autocheck mode: when non-zero 'value' set, then 'checked=checked' is assumed
|
||
|
if($this->autocheck || $this->getSetting('formBuilder')) $this->attr('checked', 'checked');
|
||
|
}
|
||
|
|
||
|
return parent::setAttribute($key, $value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set property
|
||
|
*
|
||
|
* @param string $key
|
||
|
* @param mixed $value
|
||
|
* @return InputfieldCheckbox|Inputfield
|
||
|
*
|
||
|
*/
|
||
|
public function set($key, $value) {
|
||
|
if($key === 'checkedValue' && $value != self::checkedValueDefault) {
|
||
|
$this->checkedValueIsLabel = true;
|
||
|
}
|
||
|
return parent::set($key, $value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get or set current checkbox boolean attribute state
|
||
|
*
|
||
|
* ~~~~~
|
||
|
* // the following two lines are equivalent to GET checkbox state
|
||
|
* $checked = $f->checked();
|
||
|
* $checked = !empty($f->attr('checked'));
|
||
|
*
|
||
|
* // the following two lines are equivalent to SET checkbox state
|
||
|
* $f->checked(true);
|
||
|
* $f->attr('checked', 'checked');
|
||
|
* ~~~~~
|
||
|
*
|
||
|
* @param bool|null Specify boolean to set checkbox state
|
||
|
* @return bool
|
||
|
* @since 3.0.133
|
||
|
*
|
||
|
*/
|
||
|
public function checked($checked = null) {
|
||
|
if($checked !== null) {
|
||
|
if($checked) {
|
||
|
$this->attr('checked', 'checked');
|
||
|
$checked = true;
|
||
|
} else {
|
||
|
$this->removeAttr('checked');
|
||
|
$checked = false;
|
||
|
}
|
||
|
} else {
|
||
|
$checked = $this->attr('checked');
|
||
|
$checked = empty($checked) ? false : true;
|
||
|
}
|
||
|
return $checked;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is checkbox currently checked?
|
||
|
*
|
||
|
* #pw-internal
|
||
|
*
|
||
|
* @return bool
|
||
|
*
|
||
|
*/
|
||
|
public function isChecked() {
|
||
|
return $this->checked();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Is empty (checkbox not checked)?
|
||
|
*
|
||
|
* @return bool
|
||
|
*
|
||
|
*/
|
||
|
public function isEmpty() {
|
||
|
return !$this->checked();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process input
|
||
|
*
|
||
|
* @param WireInputData $input
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function ___processInput(WireInputData $input) {
|
||
|
|
||
|
$value = $input[$this->name];
|
||
|
$checked = $this->isChecked();
|
||
|
|
||
|
if(!empty($value)) {
|
||
|
if(!$checked) $this->trackChange('value', $this->uncheckedValue, $this->checkedValue);
|
||
|
parent::attr('checked', 'checked');
|
||
|
parent::attr('value', $this->checkedValue);
|
||
|
} else {
|
||
|
if($checked) $this->trackChange('value', $this->checkedValue, $this->uncheckedValue);
|
||
|
parent::attr('checked', '');
|
||
|
parent::attr('value', $this->uncheckedValue);
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Configure checkbox field
|
||
|
*
|
||
|
* @return InputfieldWrapper
|
||
|
*
|
||
|
*/
|
||
|
public function ___getConfigInputfields() {
|
||
|
|
||
|
$inputfields = parent::___getConfigInputfields();
|
||
|
$languages = $this->wire()->languages;
|
||
|
|
||
|
/** @var InputfieldText $f */
|
||
|
$f = $this->wire()->modules->get('InputfieldText');
|
||
|
$f->attr('name', 'checkboxLabel');
|
||
|
$f->label = $this->_('Checkbox label');
|
||
|
$f->description = $this->_('If you want to have separate field and checkbox labels, specify the label that will appear next to the checkbox here.');
|
||
|
$f->notes = $this->_('If not specified, the field label will be used instead.');
|
||
|
$f->attr('value', $this->getSetting('checkboxLabel'));
|
||
|
$f->icon = 'check-square';
|
||
|
$f->collapsed = Inputfield::collapsedBlank;
|
||
|
if($languages) {
|
||
|
$f->useLanguages = true;
|
||
|
foreach($languages as $language) {
|
||
|
if(!$language->isDefault()) $f->set("value$language", $this->getSetting("checkboxLabel$language"));
|
||
|
}
|
||
|
}
|
||
|
$inputfields->add($f);
|
||
|
|
||
|
// if working with fieldtype, no additional settings are applicable
|
||
|
if($this->hasFieldtype) return $inputfields;
|
||
|
|
||
|
/** @var InputfieldText $f */
|
||
|
$f = $this->wire()->modules->get('InputfieldText');
|
||
|
$f->attr('name', 'checkedValue');
|
||
|
$f->attr('value', $this->checkedValue);
|
||
|
$f->label = $this->_('Checked Value');
|
||
|
$f->collapsed = $this->checkedValue == self::checkedValueDefault ? Inputfield::collapsedYes : Inputfield::collapsedNo;
|
||
|
$f->description = $this->_('When populated with something other than "1", this will appear as a label directly next to the checkbox.');
|
||
|
$f->required = true;
|
||
|
$inputfields->add($f);
|
||
|
|
||
|
/** @var InputfieldText $f */
|
||
|
$f = $this->wire()->modules->get('InputfieldText');
|
||
|
$f->attr('name', 'uncheckedValue');
|
||
|
$f->attr('value', "$this->uncheckedValue");
|
||
|
$f->label = $this->_('Unchecked Value');
|
||
|
$f->collapsed = $this->uncheckedValue == self::uncheckedValueDefault ? Inputfield::collapsedYes : Inputfield::collapsedNo;
|
||
|
$f->description = $this->_('This only appears in result entries, not in the form itself. You should leave this blank unless you want it to hold a specific value.');
|
||
|
$inputfields->add($f);
|
||
|
|
||
|
return $inputfields;
|
||
|
}
|
||
|
}
|