Editable   B
last analyzed

Complexity

Total Complexity 49

Size/Duplication

Total Lines 291
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 96.08%

Importance

Changes 0
Metric Value
wmc 49
lcom 1
cbo 4
dl 0
loc 291
ccs 98
cts 102
cp 0.9608
rs 8.48
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A setEditable() 0 10 3
A setEditableControl() 0 7 2
A setEditableCallback() 0 7 2
A setEditableValueCallback() 0 7 2
A setEditableRowCallback() 0 7 2
A disableEditable() 0 7 1
C setClientSideOptions() 0 35 14
A getHeaderPrototype() 0 11 2
A getCellPrototype() 0 18 5
A getEditableControl() 0 9 2
A getEditableCallback() 0 4 1
A getEditableValueCallback() 0 4 1
A getEditableRowCallback() 0 4 1
A isEditable() 0 4 1
A isEditableDisabled() 0 4 1
B handleEditable() 0 25 6
A handleEditableControl() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like Editable often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Editable, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Grido (https://github.com/o5/grido)
5
 *
6
 * Copyright (c) 2014 Petr Bugyík (http://petr.bugyik.cz)
7
 *
8
 * For the full copyright and license information, please view
9
 * the file LICENSE.md that was distributed with this source code.
10
 */
11
12
namespace Grido\Components\Columns;
13
14
use Grido\Exception;
15
16
/**
17
 * An inline editable column.
18
 *
19
 * @package     Grido
20
 * @subpackage  Components\Columns
21
 * @author      Jakub Kopřiva <[email protected]>
22
 * @author      Petr Bugyík
23
 *
24
 * @property \Nette\Forms\IControl $editableControl
25
 * @property callback $editableCallback
26
 * @property callback $editableValueCallback
27 1
 * @property callback $editableRowCallback
28
 * @property bool $editable
29
 * @property bool $editableDisabled
30
 */
31
abstract class Editable extends Column
32 1
{
33
    /** @var bool */
34
    protected $editable = FALSE;
35
36
    /** @var bool */
37
    protected $editableDisabled = FALSE;
38
39
    /** @var \Nette\Forms\IControl Custom control for inline editing */
40
    protected $editableControl;
41
42
    /** @var callback for custom handling with edited data; function($id, $newValue, $oldValue, Editable $column) {} */
43
    protected $editableCallback;
44
45
    /** @var callback for custom value; function($row, Columns\Editable $column) {} */
46
    protected $editableValueCallback;
47
48
    /** @var callback for getting row; function($row, Columns\Editable $column) {} */
49
    protected $editableRowCallback;
50
51
    /**
52
     * Sets column as editable.
53
     * @param callback $callback function($id, $newValue, $oldValue, Columns\Editable $column) {}
54
     * @param \Nette\Forms\IControl $control
55
     * @return Editable
56
     */
57
    public function setEditable($callback = NULL, \Nette\Forms\IControl $control = NULL)
58
    {
59 1
        $this->editable = TRUE;
60 1
        $this->setClientSideOptions();
61
62 1
        $callback && $this->setEditableCallback($callback);
63 1
        $control && $this->setEditableControl($control);
64
65 1
        return $this;
66
    }
67
68
    /**
69
     * Sets control for inline editation.
70
     * @param \Nette\Forms\IControl $control
71
     * @return Editable
72
     */
73
    public function setEditableControl(\Nette\Forms\IControl $control)
74
    {
75 1
        $this->isEditable() ?: $this->setEditable();
76 1
        $this->editableControl = $control;
77
78 1
        return $this;
79
    }
80
81
    /**
82
     * Sets editable callback.
83
     * @param callback $callback function($id, $newValue, $oldValue, Columns\Editable $column) {}
84
     * @return Editable
85
     */
86
    public function setEditableCallback($callback)
87
    {
88 1
        $this->isEditable() ?: $this->setEditable();
89 1
        $this->editableCallback = $callback;
90
91 1
        return $this;
92
    }
93
94
    /**
95
     * Sets editable value callback.
96
     * @param callback $callback for custom value; function($row, Columns\Editable $column) {}
97
     * @return Editable
98
     */
99
    public function setEditableValueCallback($callback)
100
    {
101 1
        $this->isEditable() ?: $this->setEditable();
102 1
        $this->editableValueCallback = $callback;
103
104 1
        return $this;
105
    }
106
107
    /**
108
     * Sets editable row callback - it's required when used editable collumn with customRenderCallback
109
     * @param callback $callback for getting row; function($id, Columns\Editable $column) {}
110
     * @return Editable
111
     */
112
    public function setEditableRowCallback($callback)
113
    {
114 1
        $this->isEditable() ?: $this->setEditable();
115 1
        $this->editableRowCallback = $callback;
116
117 1
        return $this;
118
    }
119
120
    /**
121
     * @return Editable
122
     */
123
    public function disableEditable()
124
    {
125 1
        $this->editable = FALSE;
126 1
        $this->editableDisabled = TRUE;
127
128 1
        return $this;
129
    }
130
131
    /**
132
     * @throws Exception
133
     */
134
    protected function setClientSideOptions()
135
    {
136 1
        $options = $this->grid->getClientSideOptions();
137 1
        if (!isset($options['editable'])) { //only once
138 1
            $this->grid->setClientSideOptions(['editable' => TRUE]);
139 1
            $this->grid->onRender[] = function(\Grido\Grid $grid)
140
            {
141 1
                foreach ($grid->getComponent(Column::ID)->getComponents() as $column) {
142 1
                    if (!$column instanceof Editable || !$column->isEditable()) {
143 1
                        continue;
144
                    }
145
146 1
                    $colDb = $column->getColumn();
147 1
                    $colName = $column->getName();
148 1
                    $isMissing = function ($method) use ($grid) {
149 1
                        return $grid->model instanceof \Grido\DataSources\Model
150 1
                            ? !method_exists($grid->model->dataSource, $method)
151 1
                            : TRUE;
152 1
                    };
153
154 1
                    if (($column->editableCallback === NULL && (!is_string($colDb) || strpos($colDb, '.'))) ||
155 1
                        ($column->editableCallback === NULL && $isMissing('update'))
156 1
                    ) {
157 1
                        $msg = "Column '$colName' has error: You must define callback via setEditableCallback().";
158 1
                        throw new Exception($msg);
159
                    }
160
161 1
                    if ($column->editableRowCallback === NULL && $column->customRender && $isMissing('getRow')) {
162 1
                        $msg = "Column '$colName' has error: You must define callback via setEditableRowCallback().";
163 1
                        throw new Exception($msg);
164
                    }
165 1
                }
166 1
            };
167 1
        }
168 1
    }
169
170
    /**********************************************************************************************/
171
172
    /**
173
     * Returns header cell prototype (<th> html tag).
174
     * @return \Nette\Utils\Html
175
     */
176
    public function getHeaderPrototype()
177
    {
178 1
        $th = parent::getHeaderPrototype();
179
180 1
        if ($this->isEditable()) {
181 1
            $th->setAttribute('data-grido-editable-handler', $this->link('editable!'));
182 1
            $th->setAttribute('data-grido-editableControl-handler', $this->link('editableControl!'));
183 1
        }
184
185 1
        return $th;
186
    }
187
188
    /**
189
     * Returns cell prototype (<td> html tag).
190
     * @param mixed $row
191
     * @return \Nette\Utils\Html
192
     */
193
    public function getCellPrototype($row = NULL)
194
    {
195 1
        $td = parent::getCellPrototype($row);
196
197 1
        if ($this->isEditable() && $row !== NULL) {
198 1
            if (!in_array('editable', $td->class)) {
199 1
                $td->class[] = 'editable';
200 1
            }
201
202 1
            $value = $this->editableValueCallback === NULL
203 1
                ? $this->getValue($row)
204 1
                : call_user_func_array($this->editableValueCallback, [$row, $this]);
205
206 1
            $td->setAttribute('data-grido-editable-value', $value);
207 1
        }
208
209 1
        return $td;
210
    }
211
212
    /**
213
     * Returns control for editation.
214
     * @returns \Nette\Forms\Controls\TextInput
215
     */
216
    public function getEditableControl()
217
    {
218 1
        if ($this->editableControl === NULL) {
219 1
            $this->editableControl = new \Nette\Forms\Controls\TextInput;
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Nette\Forms\Controls\TextInput() of type object<Nette\Forms\Controls\TextInput> is incompatible with the declared type object<Nette\Forms\IControl> of property $editableControl.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
220 1
            $this->editableControl->controlPrototype->class[] = 'form-control';
221 1
        }
222
223 1
        return $this->editableControl;
224
    }
225
226
    /**
227
     * @return callback
228
     * @internal
229
     */
230
    public function getEditableCallback()
231
    {
232 1
        return $this->editableCallback;
233
    }
234
235
    /**
236
     * @return callback
237
     * @internal
238
     */
239
    public function getEditableValueCallback()
240
    {
241 1
        return $this->editableValueCallback;
242
    }
243
244
    /**
245
     * @return callback
246
     * @internal
247
     */
248
    public function getEditableRowCallback()
249
    {
250 1
        return $this->editableRowCallback;
251
    }
252
253
    /**
254
     * @return bool
255
     * @internal
256
     */
257
    public function isEditable()
258
    {
259 1
        return $this->editable;
260
    }
261
262
    /**
263
     * @return bool
264
     * @internal
265
     */
266
    public function isEditableDisabled()
267
    {
268 1
        return $this->editableDisabled;
269
    }
270
271
    /**********************************************************************************************/
272
273
    /**
274
     * @internal
275
     */
276
    public function handleEditable($id, $newValue, $oldValue)
277
    {
278 1
        $this->grid->onRender($this->grid);
279
280 1
        if (!$this->presenter->isAjax() || !$this->isEditable()) {
281
            $this->presenter->terminate();
282
        }
283
284 1
        $success = $this->editableCallback
285 1
            ? call_user_func_array($this->editableCallback, [$id, $newValue, $oldValue, $this])
286 1
            : $this->grid->model->update($id, [$this->getColumn() => $newValue], $this->grid->primaryKey);
287
288 1
        if (is_callable($this->customRender)) {
289 1
            $row = $this->editableRowCallback
290 1
                ? call_user_func_array($this->editableRowCallback, [$id, $this])
291 1
                : $this->grid->model->getRow($id, $this->grid->primaryKey);
292 1
            $html = call_user_func_array($this->customRender, [$row]);
293 1
        } else {
294 1
            $html = $this->formatValue($newValue);
295
        }
296
297 1
        $payload = ['updated' => (bool) $success, 'html' => (string) $html];
298 1
        $response = new \Nette\Application\Responses\JsonResponse($payload);
299 1
        $this->presenter->sendResponse($response);
300 1
    }
301
302
    /**
303
     * @internal
304
     */
305
    public function handleEditableControl($value)
306
    {
307 1
        $this->grid->onRender($this->grid);
308
309 1
        if (!$this->presenter->isAjax() || !$this->isEditable()) {
310
            $this->presenter->terminate();
311
        }
312
313 1
        $control = $this->getEditableControl();
314 1
        $control->setValue($value);
315
316 1
        $this->getForm()->addComponent($control, 'edit' . $this->getName());
317
318 1
        $response = new \Nette\Application\Responses\TextResponse($control->getControl()->render());
319 1
        $this->presenter->sendResponse($response);
320 1
    }
321
}
322