src/Form/Control.php
<?php
declare(strict_types=1);
namespace Atk4\Ui\Form;
use Atk4\Data\Field;
use Atk4\Data\Model;
use Atk4\Data\Model\EntityFieldPair;
use Atk4\Ui\Exception;
use Atk4\Ui\Form;
use Atk4\Ui\Js\Jquery;
use Atk4\Ui\Js\JsExpression;
use Atk4\Ui\Js\JsExpressionable;
use Atk4\Ui\View;
/**
* Provides generic functionality for a form control.
*
* @phpstan-type JsCallbackSetClosure \Closure(Jquery, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): (JsExpressionable|View|string|void)
*/
class Control extends View
{
/** @var Form|null to which this field belongs */
public ?View $form = null;
/** @var EntityFieldPair<Model, Field>|null */
public ?EntityFieldPair $entityField = null;
/** @var string */
public $controlClass = '';
/** @var bool Whether you need this field to be rendered wrap in a form layout or as his */
public bool $layoutWrap = true;
/** @var bool rendered or not input label in generic Form\Layout template. */
public $renderLabel = true;
/** @var string Specify width for Fomantic-UI grid. For "four wide" use 'four'. */
public $width;
/**
* Caption is a text that must appear somewhere nearby the field. For a form with layout, this
* will typically place caption above the input field, but for checkbox this may appear next to the
* checkbox itself. If Form Layout does not have captions above the input field, then caption
* will appear as a placeholder of the input fields and it may also appear as a tooltip.
*
* Caption is usually specified by a model.
*
* @var string|null
*/
public $caption;
/**
* Placed as a pointing label below the field. This only works when Form\Control appears in a form. You can also
* set this to object, such as \Atk4\Ui\Text otherwise HTML characters are escaped.
*
* @var string|View|array<mixed>
*/
public $hint;
/** Disabled field is not editable and will not be submitted. */
public bool $disabled = false;
/** Read-only field is not editable, but will be submitted. */
public bool $readOnly = false;
#[\Override]
protected function init(): void
{
parent::init();
if ($this->form !== null && $this->entityField !== null) {
if (isset($this->form->controls[$this->entityField->getFieldName()])) {
throw (new Exception('Form field already exists'))
->addMoreInfo('name', $this->entityField->getFieldName());
}
$this->form->controls[$this->entityField->getFieldName()] = $this;
}
}
/**
* Sets the value of this field. If field is a part of the form and is associated with
* the model, then the model's value will also be affected.
*
* @param mixed $value
*/
#[\Override]
public function set($value = null)
{
if ($this->entityField !== null) {
$this->entityField->set($value);
} else {
$this->content = $value;
}
return $this;
}
#[\Override]
protected function renderView(): void
{
// it only makes sense to have "name" property inside a field if used inside a form
if ($this->form !== null) {
$this->template->trySet('name', $this->shortName);
}
parent::renderView();
}
#[\Override]
protected function renderTemplateToHtml(): string
{
$output = parent::renderTemplateToHtml();
$form = $this->getClosestOwner(Form::class);
return $form !== null ? $form->fixOwningFormAttrInRenderedHtml($output) : $output;
}
/**
* Shorthand method for on('change') event.
* Some input fields, like Calendar, could call this differently.
*
* If $expr is JsExpressionable, then it will execute it instantly.
* If $expr is callback method, then it'll make additional request to webserver.
*
* Examples:
* $control->onChange(new JsExpression('console.log(\'changed\')'));
* $control->onChange(new JsExpression('$(this).parents(\'.form\').form(\'submit\')'));
*
* @param JsExpressionable|JsCallbackSetClosure|array{JsCallbackSetClosure} $expr
* @param array<int|string, mixed>|bool $defaults
*/
public function onChange($expr, $defaults = []): void
{
if (is_bool($defaults)) {
$defaults = $defaults ? [] : ['preventDefault' => false, 'stopPropagation' => false];
}
$this->on('change', '#' . $this->name . '_input', $expr, $defaults);
}
/**
* Method similar to View::js() however will adjust selector
* to target the "input" element.
*
* $field->jsInput(true)->val(123);
*
* @param bool|string $when
* @param ($when is false ? null : JsExpressionable|null) $action
*
* @return ($action is null ? Jquery : null)
*/
public function jsInput($when = false, $action = null): ?JsExpressionable
{
return $this->js($when, $action, '#' . $this->name . '_input');
}
}