-
Notifications
You must be signed in to change notification settings - Fork 104
/
InlineEdit.php
215 lines (188 loc) · 5.74 KB
/
InlineEdit.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<?php
declare(strict_types=1);
/**
* A Simple inline editable text Vue component.
*/
namespace atk4\ui\Component;
use atk4\data\ValidationException;
use atk4\ui\Exception;
use atk4\ui\JsToast;
use atk4\ui\View;
class InlineEdit extends View
{
public $defaultTemplate = 'inline-edit.html';
/**
* JsCallback for saving data.
*
* @var \atk4\ui\JsCallback
*/
public $cb;
/**
* Input initial value.
*
* @var mixed
*/
public $initValue;
/**
* Whether callback should save value to db automatically or not.
* Default to using onChange handler.
* If set to true, then saving to db will be done when model get set
* and if model is loaded already.
*
* @var bool
*/
public $autoSave = false;
/**
* The actual db field name that need to be saved.
* Default to title field when model is set.
*
* @var string|null the name of the field
*/
public $field;
/**
* Whether component should save it's value when input get blur.
* Using this option will trigger callback when user is moving out of the
* inline edit field, like pressing tab for example.
*
* Otherwise, callback is fire when pressing Enter key,
* while inside the inline input field, only.
*
* @var bool
*/
public $saveOnBlur = true;
/**
* Default css for the input div.
*
* @var string
*/
public $inputCss = 'ui right icon input';
/**
* The validation error msg function.
* This function is call when a validation error occur and
* give you a chance to format the error msg display inside
* errorNotifier.
*
* A default one is supply if this is null.
* It receive the error ($e) as parameter.
*
* @var callable|null
*/
public $formatErrorMsg;
/**
* Initialization.
*/
public function init(): void
{
parent::init();
$this->cb = \atk4\ui\JsCallback::addTo($this);
// Set default validation error handler.
if (!$this->formatErrorMsg || !is_callable($this->formatErrorMsg)) {
$this->formatErrorMsg = function ($e, $value) {
$caption = $this->model->getField($this->field)->getCaption();
return $caption . ' - ' . $e->getMessage() . '. <br>Trying to set this value: "' . $value . '"';
};
}
}
/**
* Set Model of this View.
*
* @return \atk4\data\Model
*/
public function setModel(\atk4\data\Model $model)
{
parent::setModel($model);
$this->field = $this->field ? $this->field : $this->model->title_field;
if ($this->autoSave && $this->model->loaded()) {
$value = $_POST['value'] ?? null;
$this->cb->set(function () use ($value) {
try {
$this->model->set($this->field, $this->app->ui_persistence->typecastLoadField($this->model->getField($this->field), $value));
$this->model->save();
return $this->jsSuccess('Update successfully');
} catch (ValidationException $e) {
$this->app->terminateJson([
'success' => true,
'hasValidationError' => true,
'atkjs' => $this->jsError(call_user_func($this->formatErrorMsg, $e, $value))->jsRender(),
]);
}
});
}
return $this->model;
}
/**
* onChange handler.
* You may supply your own function to handle update.
* The function will receive one param:
* value: the new input value.
*
* @param callable $fx
*/
public function onChange($fx)
{
if (is_callable($fx) && !$this->autoSave) {
$value = $_POST['value'] ? $_POST['value'] : null;
$this->cb->set(function () use ($fx, $value) {
return call_user_func($fx, $value);
});
}
}
/**
* On success notifier.
*
* @param string $message
*
* @return \atk4\ui\JsToast
*/
public function jsSuccess($message)
{
return new JsToast([
'title' => 'Success',
'message' => $message,
'class' => 'success',
]);
}
/**
* On validation error notifier.
*
* @param string $message
*
* @return \atk4\ui\JsToast
*/
public function jsError($message)
{
return new JsToast([
'title' => 'Validation error:',
'displayTime' => 8000,
'showIcon' => 'exclamation',
'message' => $message,
'class' => 'error',
]);
}
/**
* Renders View.
*/
protected function renderView(): void
{
parent::renderView();
$type = ($this->model && $this->field) ? $this->model->getField($this->field)->type : 'text';
$type = ($type === 'string') ? 'text' : $type;
if ($type !== 'text' && $type !== 'number') {
throw new Exception('Error: Only string or number field can be edited inline. Field Type = ' . $type);
}
if ($this->model && $this->model->loaded()) {
$initValue = $this->model->get($this->field);
} else {
$initValue = $this->initValue;
}
$fieldName = $this->field ? $this->field : 'name';
$this->template->set('inputCss', $this->inputCss);
$this->template->trySet('fieldName', $fieldName);
$this->template->trySet('fieldType', $type);
$this->vue('atk-inline-edit', [
'initValue' => $initValue,
'url' => $this->cb->getJsUrl(),
'saveOnBlur' => $this->saveOnBlur,
]);
}
}