Passed
Push — master ( dc84c0...f35cb6 )
by Fabio
06:45
created

TDataGridColumn::initializeCellRendererControl()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 9
nc 5
nop 3
dl 0
loc 14
ccs 0
cts 11
cp 0
crap 30
rs 9.6111
c 1
b 0
f 0
1
<?php
2
/**
3
 * TDataGridColumn class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Web\UI\WebControls
9
 */
10
11
namespace Prado\Web\UI\WebControls;
12
13
use Exception;
14
use Prado\Prado;
15
use Prado\TPropertyValue;
16
use Prado\Exceptions\TInvalidDataValueException;
17
use Prado\Util\TDataFieldAccessor;
18
use Prado\Web\UI\TControl;
19
20
/**
21
 * TDataGridColumn class
22
 *
23
 * TDataGridColumn serves as the base class for the different column types of
24
 * the {@link TDataGrid} control.
25
 * TDataGridColumn defines the properties and methods that are common among
26
 * all datagrid column types. In particular, it initializes header and footer
27
 * cells according to {@link setHeaderText HeaderText} and {@link getHeaderStyle HeaderStyle}
28
 * {@link setFooterText FooterText} and {@link getFooterStyle FooterStyle} properties.
29
 * If {@link setHeaderImageUrl HeaderImageUrl} is specified, the image
30
 * will be displayed instead in the header cell.
31
 * The {@link getItemStyle ItemStyle} is applied to cells that belong to
32
 * non-header and -footer datagrid items.
33
 *
34
 * When the datagrid enables sorting, if the {@link setSortExpression SortExpression}
35
 * is not empty, the header cell will display a button (linkbutton or imagebutton)
36
 * that will bubble the sort command event to the datagrid.
37
 *
38
 * Since v3.1.0, TDataGridColumn has introduced two new properties {@link setHeaderRenderer HeaderRenderer}
39
 * and {@link setFooterRenderer FooterRenderer} which can be used to specify
40
 * the layout of header and footer column cells.
41
 * A renderer refers to a control class that is to be instantiated as a control.
42
 * For more details, see {@link TRepeater} and {@link TDataList}.
43
 *
44
 * Since v3.1.1, TDataGridColumn has introduced {@link setEnableCellGrouping EnableCellGrouping}.
45
 * If a column has this property set true, consecutive cells having the same content in this
46
 * column will be grouped into one cell.
47
 * Note, there are some limitations to cell grouping. We determine the cell content according to
48
 * the cell's {@link TTableCell::getText Text} property. If the text is empty and the cell has
49
 * some child controls, we will pick up the first control who implements {@link \Prado\IDataRenderer}
50
 * and obtain its {@link \Prado\IDataRenderer::getData Data} property.
51
 *
52
 * The following datagrid column types are provided by the framework currently,
53
 * - {@link TBoundColumn}, associated with a specific field in datasource and displays the corresponding data.
54
 * - {@link TEditCommandColumn}, displaying edit/update/cancel command buttons
55
 * - {@link TDropDownListColumn}, displaying a dropdown list when the item is in edit state
56
 * - {@link TButtonColumn}, displaying generic command buttons that may be bound to specific field in datasource.
57
 * - {@link THyperLinkColumn}, displaying a hyperlink that may be bound to specific field in datasource.
58
 * - {@link TCheckBoxColumn}, displaying a checkbox that may be bound to specific field in datasource.
59
 * - {@link TTemplateColumn}, displaying content based on templates.
60
 *
61
 * To create your own column class, simply override {@link initializeCell()} method,
62
 * which is the major logic for managing the data and presentation of cells in the column.
63
 *
64
 * @author Qiang Xue <[email protected]>
65
 * @package Prado\Web\UI\WebControls
66
 * @since 3.0
67
 */
68
abstract class TDataGridColumn extends \Prado\TApplicationComponent
69
{
70
	private $_id = '';
71
	private $_owner;
72
	private $_viewState = [];
73
74
	/**
75
	 * @return string the ID of the column.
76
	 */
77
	public function getID()
78
	{
79
		return $this->_id;
80
	}
81
82
	/**
83
	 * Sets the ID of the column.
84
	 * By explicitly specifying the column ID, one can access the column
85
	 * by $templateControl->ColumnID.
86
	 * @param string $value the ID of the column.
87
	 * @throws TInvalidDataValueException if the ID is of bad format
88
	 */
89
	public function setID($value)
90
	{
91
		if (!preg_match(TControl::ID_FORMAT, $value)) {
92
			throw new TInvalidDataValueException('datagridcolumn_id_invalid', get_class($this), $value);
93
		}
94
		$this->_id = $value;
95
	}
96
97
	/**
98
	 * @return string the text to be displayed in the header of this column
99
	 */
100
	public function getHeaderText()
101
	{
102
		return $this->getViewState('HeaderText', '');
103
	}
104
105
	/**
106
	 * @param string $value text to be displayed in the header of this column
107
	 */
108
	public function setHeaderText($value)
109
	{
110
		$this->setViewState('HeaderText', $value, '');
111
	}
112
113
	/**
114
	 * @return string the url of the image to be displayed in header
115
	 */
116
	public function getHeaderImageUrl()
117
	{
118
		return $this->getViewState('HeaderImageUrl', '');
119
	}
120
121
	/**
122
	 * @param string $value the url of the image to be displayed in header
123
	 */
124
	public function setHeaderImageUrl($value)
125
	{
126
		$this->setViewState('HeaderImageUrl', $value, '');
127
	}
128
129
	/**
130
	 * @return string the class name for the column header cell renderer. Defaults to empty, meaning not set.
131
	 * @since 3.1.0
132
	 */
133
	public function getHeaderRenderer()
134
	{
135
		return $this->getViewState('HeaderRenderer', '');
136
	}
137
138
	/**
139
	 * Sets the column header cell renderer class.
140
	 *
141
	 * If not empty, the class will be used to instantiate as a child control in the column header cell.
142
	 * If the class implements {@link \Prado\IDataRenderer}, the <b>Data</b> property
143
	 * will be set as the {@link getFooterText FooterText}.
144
	 *
145
	 * @param string $value the renderer class name in namespace format.
146
	 * @since 3.1.0
147
	 */
148
	public function setHeaderRenderer($value)
149
	{
150
		$this->setViewState('HeaderRenderer', $value, '');
151
	}
152
153
	/**
154
	 * @param bool $createStyle whether to create a style if previously not existing
155
	 * @return TTableItemStyle the style for header
156
	 */
157
	public function getHeaderStyle($createStyle = true)
158
	{
159
		if (($style = $this->getViewState('HeaderStyle', null)) === null && $createStyle) {
160
			$style = new TTableItemStyle;
161
			$this->setViewState('HeaderStyle', $style, null);
162
		}
163
		return $style;
164
	}
165
166
	/**
167
	 * @return string the text to be displayed in the footer of this column
168
	 */
169
	public function getFooterText()
170
	{
171
		return $this->getViewState('FooterText', '');
172
	}
173
174
	/**
175
	 * @param string $value text to be displayed in the footer of this column
176
	 */
177
	public function setFooterText($value)
178
	{
179
		$this->setViewState('FooterText', $value, '');
180
	}
181
182
	/**
183
	 * @return string the class name for the column footer cell renderer. Defaults to empty, meaning not set.
184
	 * @since 3.1.0
185
	 */
186
	public function getFooterRenderer()
187
	{
188
		return $this->getViewState('FooterRenderer', '');
189
	}
190
191
	/**
192
	 * Sets the column footer cell renderer class.
193
	 *
194
	 * If not empty, the class will be used to instantiate as a child control in the column footer cell.
195
	 * If the class implements {@link \Prado\IDataRenderer}, the <b>Data</b> property
196
	 * will be set as the {@link getFooterText FooterText}.
197
	 *
198
	 * @param string $value the renderer class name in namespace format.
199
	 * @since 3.1.0
200
	 */
201
	public function setFooterRenderer($value)
202
	{
203
		$this->setViewState('FooterRenderer', $value, '');
204
	}
205
206
	/**
207
	 * @param bool $createStyle whether to create a style if previously not existing
208
	 * @return TTableItemStyle the style for footer
209
	 */
210
	public function getFooterStyle($createStyle = true)
211
	{
212
		if (($style = $this->getViewState('FooterStyle', null)) === null && $createStyle) {
213
			$style = new TTableItemStyle;
214
			$this->setViewState('FooterStyle', $style, null);
215
		}
216
		return $style;
217
	}
218
219
	/**
220
	 * @param bool $createStyle whether to create a style if previously not existing
221
	 * @return TTableItemStyle the style for item
222
	 */
223
	public function getItemStyle($createStyle = true)
224
	{
225
		if (($style = $this->getViewState('ItemStyle', null)) === null && $createStyle) {
226
			$style = new TTableItemStyle;
227
			$this->setViewState('ItemStyle', $style, null);
228
		}
229
		return $style;
230
	}
231
232
	/**
233
	 * @return string the name of the field or expression for sorting
234
	 */
235
	public function getSortExpression()
236
	{
237
		return $this->getViewState('SortExpression', '');
238
	}
239
240
	/**
241
	 * @param string $value the name of the field or expression for sorting
242
	 */
243
	public function setSortExpression($value)
244
	{
245
		$this->setViewState('SortExpression', $value, '');
246
	}
247
248
	/**
249
	 * @return bool whether cells having the same content should be grouped together. Defaults to false.
250
	 * @since 3.1.1
251
	 */
252
	public function getEnableCellGrouping()
253
	{
254
		return $this->getViewState('EnableCellGrouping', false);
255
	}
256
257
	/**
258
	 * @param bool $value whether cells having the same content should be grouped together.
259
	 * @since 3.1.1
260
	 */
261
	public function setEnableCellGrouping($value)
262
	{
263
		$this->setViewState('EnableCellGrouping', TPropertyValue::ensureBoolean($value), false);
264
	}
265
266
	/**
267
	 * @param mixed $checkParents
268
	 * @return bool whether the column is visible. Defaults to true.
269
	 */
270
	public function getVisible($checkParents = true)
0 ignored issues
show
Unused Code introduced by
The parameter $checkParents is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

270
	public function getVisible(/** @scrutinizer ignore-unused */ $checkParents = true)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
271
	{
272
		return $this->getViewState('Visible', true);
273
	}
274
275
	/**
276
	 * @param bool $value whether the column is visible
277
	 */
278
	public function setVisible($value)
279
	{
280
		$this->setViewState('Visible', TPropertyValue::ensureBoolean($value), true);
281
	}
282
283
	/**
284
	 * Returns a viewstate value.
285
	 *
286
	 * @param string $key the name of the viewstate value to be returned
287
	 * @param mixed $defaultValue the default value. If $key is not found in viewstate, $defaultValue will be returned
288
	 * @return mixed the viewstate value corresponding to $key
289
	 */
290
	protected function getViewState($key, $defaultValue = null)
291
	{
292
		return $this->_viewState[$key] ?? $defaultValue;
293
	}
294
295
	/**
296
	 * Sets a viewstate value.
297
	 *
298
	 * Make sure that the viewstate value must be serializable and unserializable.
299
	 * @param string $key the name of the viewstate value
300
	 * @param mixed $value the viewstate value to be set
301
	 * @param null|mixed $defaultValue default value. If $value===$defaultValue, the item will be cleared from the viewstate.
302
	 */
303
	protected function setViewState($key, $value, $defaultValue = null)
304
	{
305
		if ($value === $defaultValue) {
306
			unset($this->_viewState[$key]);
307
		} else {
308
			$this->_viewState[$key] = $value;
309
		}
310
	}
311
312
	/**
313
	 * Loads persistent state values.
314
	 * @param mixed $state state values
315
	 */
316
	public function loadState($state)
317
	{
318
		$this->_viewState = $state;
319
	}
320
321
	/**
322
	 * Saves persistent state values.
323
	 * @return mixed values to be saved
324
	 */
325
	public function saveState()
326
	{
327
		return $this->_viewState;
328
	}
329
330
	/**
331
	 * @return TDataGrid datagrid that owns this column
332
	 */
333
	public function getOwner()
334
	{
335
		return $this->_owner;
336
	}
337
338
	/**
339
	 * @param TDataGrid $value datagrid object that owns this column
340
	 */
341
	public function setOwner(TDataGrid $value)
342
	{
343
		$this->_owner = $value;
344
	}
345
346
	/**
347
	 * Initializes the column.
348
	 * This method is invoked by {@link TDataGrid} when the column
349
	 * is about to be used to initialize datagrid items.
350
	 * Derived classes may override this method to do additional initialization.
351
	 */
352
	public function initialize()
353
	{
354
	}
355
356
	/**
357
	 * Fetches the value of the data at the specified field.
358
	 * If the data is an array, the field is used as an array key.
359
	 * If the data is an of {@link TMap}, {@link TList} or their derived class,
360
	 * the field is used as a key value.
361
	 * If the data is a component, the field is used as the name of a property.
362
	 * @param mixed $data data containing the field of value
363
	 * @param string $field the data field
364
	 * @throws TInvalidDataValueException if the data or the field is invalid.
365
	 * @return mixed data value at the specified field
366
	 */
367
	protected function getDataFieldValue($data, $field)
368
	{
369
		return TDataFieldAccessor::getDataFieldValue($data, $field);
370
	}
371
372
373
	/**
374
	 * Initializes the specified cell to its initial values.
375
	 * The default implementation sets the content of header and footer cells.
376
	 * If sorting is enabled by the grid and sort expression is specified in the column,
377
	 * the header cell will show a link/image button. Otherwise, the header/footer cell
378
	 * will only show static text/image.
379
	 * This method can be overriden to provide customized intialization to column cells.
380
	 * @param TTableCell $cell the cell to be initialized.
381
	 * @param int $columnIndex the index to the Columns property that the cell resides in.
382
	 * @param string $itemType the type of cell (Header,Footer,Item,AlternatingItem,EditItem,SelectedItem)
383
	 */
384
	public function initializeCell($cell, $columnIndex, $itemType)
385
	{
386
		if ($itemType === TListItemType::Header) {
387
			$this->initializeHeaderCell($cell, $columnIndex);
388
		} elseif ($itemType === TListItemType::Footer) {
389
			$this->initializeFooterCell($cell, $columnIndex);
390
		}
391
	}
392
393
	/**
394
	 * Returns a value indicating whether this column allows sorting.
395
	 * The column allows sorting only when {@link getSortExpression SortExpression}
396
	 * is not empty and the datagrid allows sorting.
397
	 * @return bool whether this column allows sorting
398
	 */
399
	public function getAllowSorting()
400
	{
401
		return $this->getSortExpression() !== '' && (!$this->_owner || $this->_owner->getAllowSorting());
402
	}
403
404
	/**
405
	 * Initializes the header cell.
406
	 *
407
	 * This method attempts to use {@link getHeaderRenderer HeaderRenderer} to
408
	 * instantiate the header cell. If that is not available, it will populate
409
	 * the cell with an image or a text string, depending on {@link getHeaderImageUrl HeaderImageUrl}
410
	 * and {@link getHeaderText HeaderText} property values.
411
	 *
412
	 * If the column allows sorting, image or text will be created as
413
	 * a button which issues <b>Sort</b> command upon user click.
414
	 *
415
	 * @param TTableCell $cell the cell to be initialized
416
	 * @param int $columnIndex the index to the Columns property that the cell resides in.
417
	 */
418
	protected function initializeHeaderCell($cell, $columnIndex)
0 ignored issues
show
Unused Code introduced by
The parameter $columnIndex is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

418
	protected function initializeHeaderCell($cell, /** @scrutinizer ignore-unused */ $columnIndex)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
419
	{
420
		$text = $this->getHeaderText();
421
		if (($classPath = $this->getHeaderRenderer()) !== '') {
422
			$this->initializeCellRendererControl($cell, $classPath, $text);
423
		} elseif ($this->getAllowSorting()) {
424
			$sortExpression = $this->getSortExpression();
425
			if (($url = $this->getHeaderImageUrl()) !== '') {
426
				$button = new TImageButton;
427
				$button->setImageUrl($url);
428
				$button->setCommandName(TDataGrid::CMD_SORT);
429
				$button->setCommandParameter($sortExpression);
430
				if ($text !== '') {
431
					$button->setAlternateText($text);
432
				}
433
				$button->setCausesValidation(false);
434
				$cell->getControls()->add($button);
435
			} elseif ($text !== '') {
436
				$button = new TLinkButton;
437
				$button->setText($text);
438
				$button->setCommandName(TDataGrid::CMD_SORT);
439
				$button->setCommandParameter($sortExpression);
440
				$button->setCausesValidation(false);
441
				$cell->getControls()->add($button);
442
			} else {
443
				$cell->setText('&nbsp;');
444
			}
445
		} else {
446
			if (($url = $this->getHeaderImageUrl()) !== '') {
447
				$image = new TImage;
448
				$image->setImageUrl($url);
449
				if ($text !== '') {
450
					$image->setAlternateText($text);
451
				}
452
				$cell->getControls()->add($image);
453
			} elseif ($text !== '') {
454
				$cell->setText($text);
455
			} else {
456
				$cell->setText('&nbsp;');
457
			}
458
		}
459
	}
460
461
	/**
462
	 * Initializes the footer cell.
463
	 *
464
	 * This method attempts to use {@link getFooterRenderer FooterRenderer} to
465
	 * instantiate the footer cell. If that is not available, it will populate
466
	 * the cell with a text string specified by {@link getFooterImageUrl FooterImageUrl}
467
	 *
468
	 * @param TTableCell $cell the cell to be initialized
469
	 * @param int $columnIndex the index to the Columns property that the cell resides in.
470
	 */
471
	protected function initializeFooterCell($cell, $columnIndex)
0 ignored issues
show
Unused Code introduced by
The parameter $columnIndex is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

471
	protected function initializeFooterCell($cell, /** @scrutinizer ignore-unused */ $columnIndex)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
472
	{
473
		$text = $this->getFooterText();
474
		if (($classPath = $this->getFooterRenderer()) !== '') {
475
			$this->initializeCellRendererControl($cell, $classPath, $text);
476
		} elseif ($text !== '') {
477
			$cell->setText($text);
478
		} else {
479
			$cell->setText('&nbsp;');
480
		}
481
	}
482
483
	/**
484
	 * Initializes a cell creating a renderer control.
485
	 *
486
	 * @param TTableCell $cell the cell to be initialized
487
	 * @param string $classPath the rendered class that will be instanciated.
488
	 * @param mixed $data used to initialize the control
489
	 */
490
	protected function initializeCellRendererControl($cell, $classPath, $data = null)
491
	{
492
		$control = Prado::createComponent($classPath);
493
		$cell->getControls()->add($control);
494
		if ($control instanceof \Prado\IDataRenderer) {
495
			if ($control instanceof IItemDataRenderer && ($item = $cell->getParent()) instanceof IItemDataRenderer) {
496
				$control->setItemIndex($item->getItemIndex());
0 ignored issues
show
Bug introduced by
The method getItemIndex() does not exist on Prado\Web\UI\TControl. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

496
				$control->setItemIndex($item->/** @scrutinizer ignore-call */ getItemIndex());
Loading history...
497
				$control->setItemType($item->getItemType());
0 ignored issues
show
Bug introduced by
The method getItemType() does not exist on Prado\Web\UI\TControl. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

497
				$control->setItemType($item->/** @scrutinizer ignore-call */ getItemType());
Loading history...
498
			}
499
			if($data !== null) {
500
				$control->setData($data);
501
			}
502
		}
503
		return $control;
504
	}
505
506
	/**
507
	 * Formats the text value according to a format string.
508
	 * If the format string is empty, the original value is converted into
509
	 * a string and returned.
510
	 * If the format string starts with '#', the string is treated as a PHP expression
511
	 * within which the token '{0}' is translated with the data value to be formated.
512
	 * Otherwise, the format string and the data value are passed
513
	 * as the first and second parameters in {@link sprintf}.
514
	 * @param string $formatString format string
515
	 * @param mixed $value the data to be formatted
516
	 * @return string the formatted result
517
	 */
518
	protected function formatDataValue($formatString, $value)
519
	{
520
		if ($formatString === '') {
521
			return TPropertyValue::ensureString($value);
522
		} elseif ($formatString[0] === '#') {
523
			$expression = strtr(substr($formatString, 1), ['{0}' => '$value']);
524
			try {
525
				return eval("return $expression;");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
526
			} catch (Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
527
				throw new TInvalidDataValueException('datagridcolumn_expression_invalid', get_class($this), $expression, $e->getMessage());
528
			}
529
		} else {
530
			return sprintf($formatString, $value);
531
		}
532
	}
533
}
534