1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Form\Control;
6:
7: use Atk4\Data\Model;
8: use Atk4\Ui\Form;
9: use Atk4\Ui\Js\Jquery;
10: use Atk4\Ui\Js\JsBlock;
11:
12: /**
13: * Dropdown form control that will based it's list value
14: * according to another input value.
15: * Also possible to cascade value from another cascade field.
16: */
17: class DropdownCascade extends Dropdown
18: {
19: /** @var string|Form\Control The form control to use for setting this dropdown list values from. */
20: public $cascadeFrom;
21:
22: /** @var string|Model|null The hasMany reference model that will generate value for this dropdown list. */
23: public $reference;
24:
25: #[\Override]
26: protected function init(): void
27: {
28: parent::init();
29:
30: if (!$this->cascadeFrom instanceof Form\Control) {
31: $this->cascadeFrom = $this->form->getControl($this->cascadeFrom);
32: }
33:
34: $cascadeFromValue = $this->getApp()->hasRequestPostParam($this->cascadeFrom->name)
35: ? $this->getApp()->uiPersistence->typecastLoadField($this->cascadeFrom->entityField->getField(), $this->getApp()->getRequestPostParam($this->cascadeFrom->name))
36: : $this->cascadeFrom->entityField->get();
37:
38: $this->model = $this->cascadeFrom->model ? $this->cascadeFrom->model->ref($this->reference) : null;
39:
40: // populate default dropdown values
41: $this->dropdownOptions['values'] = $this->getJsValues($this->getNewValues($cascadeFromValue), $this->entityField->get());
42:
43: // JS to execute for the onChange handler of the parent dropdown
44: $expr = [
45: function (Jquery $j) use ($cascadeFromValue) {
46: return new JsBlock([
47: $this->jsDropdown()->dropdown('change values', $this->getNewValues($cascadeFromValue)),
48: $this->jsDropdown()->removeClass('loading'),
49: ]);
50: },
51: $this->jsDropdown()->dropdown('clear'),
52: $this->jsDropdown()->addClass('loading'),
53: ];
54:
55: $this->cascadeFrom->onChange($expr, ['args' => [$this->cascadeFrom->name => $this->cascadeFrom->jsInput()->val()]]);
56: }
57:
58: #[\Override]
59: public function set($value = null)
60: {
61: $this->dropdownOptions['values'] = $this->getJsValues($this->getNewValues($this->cascadeFrom->entityField->get()), $value);
62:
63: return parent::set($value);
64: }
65:
66: /**
67: * Generate new dropdown values based on cascadeInput model selected ID.
68: * Return an empty value set if ID is null.
69: *
70: * @param string|int $id
71: */
72: public function getNewValues($id): array
73: {
74: if (!$id) {
75: return [['value' => '', 'text' => $this->empty, 'name' => $this->empty]];
76: }
77:
78: $model = $this->cascadeFrom->model->load($id)->ref($this->reference);
79: $values = [];
80: foreach ($model as $k => $row) {
81: if ($this->renderRowFunction) {
82: $res = ($this->renderRowFunction)($row, $k);
83: $values[] = ['value' => $res['value'], 'text' => $res['title'], 'name' => $res['title']];
84: } else {
85: $values[] = ['value' => $row->getId(), 'text' => $row->get($model->titleField), 'name' => $row->get($model->titleField)];
86: }
87: }
88:
89: return $values;
90: }
91:
92: /**
93: * Will mark current value as selected from a list
94: * of possible values.
95: *
96: * @param string|int $value the current field value
97: */
98: private function getJsValues(array $values, $value): array
99: {
100: foreach ($values as $k => $v) {
101: if ($v['value'] === $value) {
102: $values[$k]['selected'] = true;
103:
104: break;
105: }
106: }
107:
108: return $values;
109: }
110:
111: #[\Override]
112: protected function htmlRenderValue(): void
113: {
114: // called in parent::renderView(), but values are rendered only via JS
115: }
116:
117: #[\Override]
118: protected function renderView(): void
119: {
120: // multiple selection is not supported
121: $this->multiple = false;
122:
123: parent::renderView();
124: }
125: }
126: