TDataGrid::buildNextPrevPager()   B
last analyzed

Complexity

Conditions 7
Paths 16

Size

Total Lines 41
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 33
c 1
b 0
f 0
nc 16
nop 1
dl 0
loc 41
ccs 0
cts 39
cp 0
crap 56
rs 8.4586
1
<?php
2
3
/**
4
 * TDataGrid related class files.
5
 * This file contains the definition of the following classes:
6
 * TDataGrid, TDataGridItem, TDataGridItemCollection, TDataGridColumnCollection,
7
 * TDataGridPagerStyle, TDataGridItemEventParameter,
8
 * TDataGridCommandEventParameter, TDataGridSortCommandEventParameter,
9
 * TDataGridPageChangedEventParameter
10
 *
11
 * @author Qiang Xue <[email protected]>
12
 * @link https://github.com/pradosoft/prado
13
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
14
 */
15
16
namespace Prado\Web\UI\WebControls;
17
18
use Prado\Collections\TList;
19
use Prado\Collections\TPagedDataSource;
20
use Prado\TPropertyValue;
21
use Prado\Exceptions\TInvalidDataTypeException;
22
use Prado\Web\UI\ITemplate;
23
24
/**
25
 * TDataGrid class
26
 *
27
 * TDataGrid represents a data bound and updatable grid control.
28
 *
29
 * To populate data into the datagrid, sets its {@see setDataSource DataSource}
30
 * to a tabular data source and call {@see dataBind()}.
31
 * Each row of data will be represented by an item in the {@see getItems Items}
32
 * collection of the datagrid.
33
 *
34
 * An item can be at one of three states: browsing, selected and edit.
35
 * The state determines how the item will be displayed. For example, if an item
36
 * is in edit state, it may be displayed as a table row with input text boxes
37
 * if the columns are of type {@see \Prado\Web\UI\WebControls\TBoundColumn}; and if in browsing state,
38
 * they are displayed as static text.
39
 *
40
 * To change the state of an item, set {@see setEditItemIndex EditItemIndex}
41
 * or {@see setSelectedItemIndex SelectedItemIndex} property.
42
 *
43
 * Each datagrid item has a {@see \Prado\Web\UI\WebControls\TDataGridItem::getItemType type}
44
 * which tells the position and state of the item in the datalist. An item in the header
45
 * of the repeater is of type Header. A body item may be of either
46
 * Item, AlternatingItem, SelectedItem or EditItem, depending whether the item
47
 * index is odd or even, whether it is being selected or edited.
48
 *
49
 * A datagrid is specified with a list of columns. Each column specifies how the corresponding
50
 * table column will be displayed. For example, the header/footer text of that column,
51
 * the cells in that column, and so on. The following column types are currently
52
 * provided by the framework,
53
 * - {@see \Prado\Web\UI\WebControls\TBoundColumn}, associated with a specific field in datasource and displays the corresponding data.
54
 * - {@see \Prado\Web\UI\WebControls\TEditCommandColumn}, displaying edit/update/cancel command buttons
55
 * - {@see \Prado\Web\UI\WebControls\TButtonColumn}, displaying generic command buttons that may be bound to specific field in datasource.
56
 * - {@see \Prado\Web\UI\WebControls\TDropDownListColumn}, displaying a dropdown list when the item is in edit state
57
 * - {@see \Prado\Web\UI\WebControls\THyperLinkColumn}, displaying a hyperlink that may be bound to specific field in datasource.
58
 * - {@see \Prado\Web\UI\WebControls\TCheckBoxColumn}, displaying a checkbox that may be bound to specific field in datasource.
59
 * - {@see \Prado\Web\UI\WebControls\TTemplateColumn}, displaying content based on templates.
60
 *
61
 * There are three ways to specify columns for a datagrid.
62
 * <ul>
63
 *  <li>Automatically generated based on data source.
64
 *  By setting {@see setAutoGenerateColumns AutoGenerateColumns} to true,
65
 *  a list of columns will be automatically generated based on the schema of the data source.
66
 *  Each column corresponds to a column of the data.</li>
67
 *  <li>Specified in template. For example,
68
 *  ```php
69
 *     <com:TDataGrid ...>
70
 *        <com:TBoundColumn .../>
71
 *        <com:TEditCommandColumn .../>
72
 *     </com:TDataGrid>
73
 *  ```
74
 *  </li>
75
 *  <li>Manually created in code. Columns can be manipulated via
76
 *  the {@see setColumns Columns} property of the datagrid. For example,
77
 * ```php
78
 *    $column=new TBoundColumn;
79
 *    $datagrid->Columns[]=$column;
80
 * ```
81
 *  </li>
82
 * </ul>
83
 * Note, automatically generated columns cannot be accessed via
84
 * the {@see getColumns Columns} property.
85
 *
86
 * TDataGrid supports sorting. If the {@see setAllowSorting AllowSorting}
87
 * is set to true, a column with nonempty {@see setSortExpression SortExpression}
88
 * will have its header text displayed as a clickable link button.
89
 * Clicking on the link button will raise {@see onSortCommand OnSortCommand}
90
 * event. You can respond to this event, sort the data source according
91
 * to the event parameter, and then invoke {@see databind()} on the datagrid
92
 * to show to end users the sorted data.
93
 *
94
 * TDataGrid supports paging. If the {@see setAllowPaging AllowPaging}
95
 * is set to true, a pager will be displayed on top and/or bottom of the table.
96
 * How the pager will be displayed is determined by the {@see getPagerStyle PagerStyle}
97
 * property. Clicking on a pager button will raise an {@see onPageIndexChanged OnPageIndexChanged}
98
 * event. You can respond to this event, specify the page to be displayed by
99
 * setting {@see setCurrentPageIndex CurrentPageIndex}</b> property,
100
 * and then invoke {@see databind()} on the datagrid to show to end users
101
 * a new page of data.
102
 *
103
 * TDataGrid supports two kinds of paging. The first one is based on the number of data items in
104
 * datasource. The number of pages {@see getPageCount PageCount} is calculated based
105
 * the item number and the {@see setPageSize PageSize} property.
106
 * The datagrid will manage which section of the data source to be displayed
107
 * based on the {@see setCurrentPageIndex CurrentPageIndex} property.
108
 * The second approach calculates the page number based on the
109
 * {@see setVirtualItemCount VirtualItemCount} property and
110
 * the {@see setPageSize PageSize} property. The datagrid will always
111
 * display from the beginning of the datasource up to the number of
112
 * {@see setPageSize PageSize} data items. This approach is especially
113
 * useful when the datasource may contain too many data items to be managed by
114
 * the datagrid efficiently.
115
 *
116
 * When the datagrid contains a button control that raises an {@see onCommand OnCommand}
117
 * event, the event will be bubbled up to the datagrid control.
118
 * If the event's command name is recognizable by the datagrid control,
119
 * a corresponding item event will be raised. The following item events will be
120
 * raised upon a specific command:
121
 * - OnEditCommand, if CommandName=edit
122
 * - OnCancelCommand, if CommandName=cancel
123
 * - OnSelectCommand, if CommandName=select
124
 * - OnDeleteCommand, if CommandName=delete
125
 * - OnUpdateCommand, if CommandName=update
126
 * - onPageIndexChanged, if CommandName=page
127
 * - OnSortCommand, if CommandName=sort
128
 * Note, an {@see onItemCommand OnItemCommand} event is raised in addition to
129
 * the above specific command events.
130
 *
131
 * TDataGrid also raises an {@see onItemCreated OnItemCreated} event for
132
 * every newly created datagrid item. You can respond to this event to customize
133
 * the content or style of the newly created item.
134
 *
135
 * Note, the data bound to the datagrid are reset to null after databinding.
136
 * There are several ways to access the data associated with a datagrid row:
137
 * - Access the data in {@see onItemDataBound OnItemDataBound} event
138
 * - Use {@see getDataKeys DataKeys} to obtain the data key associated with
139
 * the specified datagrid row and use the key to fetch the corresponding data
140
 * from some persistent storage such as DB.
141
 * - Save the data in viewstate and get it back during postbacks.
142
 *
143
 * @author Qiang Xue <[email protected]>
144
 * @since 3.0
145
 * @method TTableStyle getStyle()
146
 */
147
class TDataGrid extends TBaseDataList implements \Prado\Web\UI\INamingContainer
148
{
149
	/**
150
	 * Command name that TDataGrid understands.
151
	 */
152
	public const CMD_SELECT = 'Select';
153
	public const CMD_EDIT = 'Edit';
154
	public const CMD_UPDATE = 'Update';
155
	public const CMD_DELETE = 'Delete';
156
	public const CMD_CANCEL = 'Cancel';
157
	public const CMD_SORT = 'Sort';
158
	public const CMD_PAGE = 'Page';
159
	public const CMD_PAGE_NEXT = 'Next';
160
	public const CMD_PAGE_PREV = 'Previous';
161
	public const CMD_PAGE_FIRST = 'First';
162
	public const CMD_PAGE_LAST = 'Last';
163
164
	/**
165
	 * @var TDataGridColumnCollection manually created column collection
166
	 */
167
	private $_columns;
168
	/**
169
	 * @var TDataGridColumnCollection automatically created column collection
170
	 */
171
	private $_autoColumns;
172
	/**
173
	 * @var TList all columns including both manually and automatically created columns
174
	 */
175
	private $_allColumns;
176
	/**
177
	 * @var TDataGridItemCollection datagrid item collection
178
	 */
179
	private $_items;
180
	/**
181
	 * @var TDataGridItem header item
182
	 */
183
	private $_header;
184
	/**
185
	 * @var TDataGridItem footer item
186
	 */
187
	private $_footer;
188
	/**
189
	 * @var TPagedDataSource paged data source object
190
	 */
191
	private $_pagedDataSource;
0 ignored issues
show
introduced by
The private property $_pagedDataSource is not used, and could be removed.
Loading history...
192
	private $_topPager;
193
	private $_bottomPager;
194
	/**
195
	 * @var ITemplate template used when empty data is bounded
196
	 */
197
	private $_emptyTemplate;
198
	/**
199
	 * @var bool whether empty template is effective
200
	 */
201
	private $_useEmptyTemplate = false;
202
203
	/**
204
	 * @return string tag name (table) of the datagrid
205
	 */
206
	protected function getTagName()
207
	{
208
		return 'table';
209
	}
210
211
	/**
212
	 * @return string Name of the class used in AutoGenerateColumns mode
213
	 */
214
	protected function getAutoGenerateColumnName()
215
	{
216
		return 'TBoundColumn';
217
	}
218
219
	/**
220
	 * Adds objects parsed in template to datagrid.
221
	 * Datagrid columns are added into {@see getColumns Columns} collection.
222
	 * @param mixed $object object parsed in template
223
	 */
224
	public function addParsedObject($object)
225
	{
226
		if ($object instanceof TDataGridColumn) {
227
			$this->getColumns()->add($object);
228
		} else {
229
			parent::addParsedObject($object);
230
		}  // this is needed by EmptyTemplate
231
	}
232
233
	/**
234
	 * @return TDataGridColumnCollection manually specified datagrid columns
235
	 */
236
	public function getColumns()
237
	{
238
		if (!$this->_columns) {
239
			$this->_columns = new TDataGridColumnCollection($this);
240
		}
241
		return $this->_columns;
242
	}
243
244
	/**
245
	 * @return TDataGridColumnCollection automatically generated datagrid columns
246
	 */
247
	public function getAutoColumns()
248
	{
249
		if (!$this->_autoColumns) {
250
			$this->_autoColumns = new TDataGridColumnCollection($this);
251
		}
252
		return $this->_autoColumns;
253
	}
254
255
	/**
256
	 * @return TDataGridItemCollection datagrid item collection
257
	 */
258
	public function getItems()
259
	{
260
		if (!$this->_items) {
261
			$this->_items = new TDataGridItemCollection();
262
		}
263
		return $this->_items;
264
	}
265
266
	/**
267
	 * @return int number of items
268
	 */
269
	public function getItemCount()
270
	{
271
		return $this->_items ? $this->_items->getCount() : 0;
272
	}
273
274
	/**
275
	 * Creates a style object for the control.
276
	 * This method creates a {@see \Prado\Web\UI\WebControls\TTableStyle} to be used by datagrid.
277
	 * @return TTableStyle control style to be used
278
	 */
279
	protected function createStyle()
280
	{
281
		return new TTableStyle();
282
	}
283
284
	/**
285
	 * @return string the URL of the background image for the datagrid
286
	 */
287
	public function getBackImageUrl()
288
	{
289
		return $this->getStyle()->getBackImageUrl();
290
	}
291
292
	/**
293
	 * @param string $value the URL of the background image for the datagrid
294
	 */
295
	public function setBackImageUrl($value)
296
	{
297
		$this->getStyle()->setBackImageUrl($value);
298
	}
299
300
	/**
301
	 * @return TTableItemStyle the style for every item
302
	 */
303
	public function getItemStyle()
304
	{
305
		if (($style = $this->getViewState('ItemStyle', null)) === null) {
306
			$style = new TTableItemStyle();
307
			$this->setViewState('ItemStyle', $style, null);
308
		}
309
		return $style;
310
	}
311
312
	/**
313
	 * @return TTableItemStyle the style for each alternating item
314
	 */
315
	public function getAlternatingItemStyle()
316
	{
317
		if (($style = $this->getViewState('AlternatingItemStyle', null)) === null) {
318
			$style = new TTableItemStyle();
319
			$this->setViewState('AlternatingItemStyle', $style, null);
320
		}
321
		return $style;
322
	}
323
324
	/**
325
	 * @return TTableItemStyle the style for selected item
326
	 */
327
	public function getSelectedItemStyle()
328
	{
329
		if (($style = $this->getViewState('SelectedItemStyle', null)) === null) {
330
			$style = new TTableItemStyle();
331
			$this->setViewState('SelectedItemStyle', $style, null);
332
		}
333
		return $style;
334
	}
335
336
	/**
337
	 * @return TTableItemStyle the style for edit item
338
	 */
339
	public function getEditItemStyle()
340
	{
341
		if (($style = $this->getViewState('EditItemStyle', null)) === null) {
342
			$style = new TTableItemStyle();
343
			$this->setViewState('EditItemStyle', $style, null);
344
		}
345
		return $style;
346
	}
347
348
	/**
349
	 * @return TTableItemStyle the style for header
350
	 */
351
	public function getHeaderStyle()
352
	{
353
		if (($style = $this->getViewState('HeaderStyle', null)) === null) {
354
			$style = new TTableItemStyle();
355
			$this->setViewState('HeaderStyle', $style, null);
356
		}
357
		return $style;
358
	}
359
360
	/**
361
	 * @return TTableItemStyle the style for footer
362
	 */
363
	public function getFooterStyle()
364
	{
365
		if (($style = $this->getViewState('FooterStyle', null)) === null) {
366
			$style = new TTableItemStyle();
367
			$this->setViewState('FooterStyle', $style, null);
368
		}
369
		return $style;
370
	}
371
372
	/**
373
	 * @return TDataGridPagerStyle the style for pager
374
	 */
375
	public function getPagerStyle()
376
	{
377
		if (($style = $this->getViewState('PagerStyle', null)) === null) {
378
			$style = new TDataGridPagerStyle();
379
			$this->setViewState('PagerStyle', $style, null);
380
		}
381
		return $style;
382
	}
383
384
	/**
385
	 * @return TStyle the style for thead element, if any
386
	 * @since 3.1.1
387
	 */
388
	public function getTableHeadStyle()
389
	{
390
		if (($style = $this->getViewState('TableHeadStyle', null)) === null) {
391
			$style = new TStyle();
392
			$this->setViewState('TableHeadStyle', $style, null);
393
		}
394
		return $style;
395
	}
396
397
	/**
398
	 * @return TStyle the style for tbody element, if any
399
	 * @since 3.1.1
400
	 */
401
	public function getTableBodyStyle()
402
	{
403
		if (($style = $this->getViewState('TableBodyStyle', null)) === null) {
404
			$style = new TStyle();
405
			$this->setViewState('TableBodyStyle', $style, null);
406
		}
407
		return $style;
408
	}
409
410
	/**
411
	 * @return TStyle the style for tfoot element, if any
412
	 * @since 3.1.1
413
	 */
414
	public function getTableFootStyle()
415
	{
416
		if (($style = $this->getViewState('TableFootStyle', null)) === null) {
417
			$style = new TStyle();
418
			$this->setViewState('TableFootStyle', $style, null);
419
		}
420
		return $style;
421
	}
422
423
	/**
424
	 * @return string caption for the datagrid
425
	 */
426
	public function getCaption()
427
	{
428
		return $this->getViewState('Caption', '');
429
	}
430
431
	/**
432
	 * @param string $value caption for the datagrid
433
	 */
434
	public function setCaption($value)
435
	{
436
		$this->setViewState('Caption', $value, '');
437
	}
438
439
	/**
440
	 * @return TTableCaptionAlign datagrid caption alignment. Defaults to TTableCaptionAlign::NotSet.
441
	 */
442
	public function getCaptionAlign()
443
	{
444
		return $this->getViewState('CaptionAlign', TTableCaptionAlign::NotSet);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getViewSta...leCaptionAlign::NotSet) also could return the type string which is incompatible with the documented return type Prado\Web\UI\WebControls\TTableCaptionAlign.
Loading history...
445
	}
446
447
	/**
448
	 * @param TTableCaptionAlign $value datagrid caption alignment. Valid values include
449
	 */
450
	public function setCaptionAlign($value)
451
	{
452
		$this->setViewState('CaptionAlign', TPropertyValue::ensureEnum($value, TTableCaptionAlign::class), TTableCaptionAlign::NotSet);
453
	}
454
455
	/**
456
	 * @return TDataGridItem the header item
457
	 */
458
	public function getHeader()
459
	{
460
		return $this->_header;
461
	}
462
463
	/**
464
	 * @return TDataGridItem the footer item
465
	 */
466
	public function getFooter()
467
	{
468
		return $this->_footer;
469
	}
470
471
	/**
472
	 * @return TDataGridPager the pager displayed at the top of datagrid. It could be null if paging is disabled.
473
	 */
474
	public function getTopPager()
475
	{
476
		return $this->_topPager;
477
	}
478
479
	/**
480
	 * @return TDataGridPager the pager displayed at the bottom of datagrid. It could be null if paging is disabled.
481
	 */
482
	public function getBottomPager()
483
	{
484
		return $this->_bottomPager;
485
	}
486
487
	/**
488
	 * @return TDataGridItem the selected item, null if no item is selected.
489
	 */
490
	public function getSelectedItem()
491
	{
492
		$index = $this->getSelectedItemIndex();
493
		$items = $this->getItems();
494
		if ($index >= 0 && $index < $items->getCount()) {
495
			return $items->itemAt($index);
496
		} else {
497
			return null;
498
		}
499
	}
500
501
	/**
502
	 * @return int the zero-based index of the selected item in {@see getItems Items}.
503
	 * A value -1 means no item selected.
504
	 */
505
	public function getSelectedItemIndex()
506
	{
507
		return $this->getViewState('SelectedItemIndex', -1);
508
	}
509
510
	/**
511
	 * Selects an item by its index in {@see getItems Items}.
512
	 * Previously selected item will be un-selected.
513
	 * If the item to be selected is already in edit mode, it will remain in edit mode.
514
	 * If the index is less than 0, any existing selection will be cleared up.
515
	 * @param int $value the selected item index
516
	 */
517
	public function setSelectedItemIndex($value)
518
	{
519
		if (($value = TPropertyValue::ensureInteger($value)) < 0) {
520
			$value = -1;
521
		}
522
		if (($current = $this->getSelectedItemIndex()) !== $value) {
523
			$this->setViewState('SelectedItemIndex', $value, -1);
524
			$items = $this->getItems();
525
			$itemCount = $items->getCount();
526
			if ($current >= 0 && $current < $itemCount) {
527
				$item = $items->itemAt($current);
528
				if ($item->getItemType() !== TListItemType::EditItem) {
529
					$item->setItemType($current % 2 ? TListItemType::AlternatingItem : TListItemType::Item);
530
				}
531
			}
532
			if ($value >= 0 && $value < $itemCount) {
533
				$item = $items->itemAt($value);
534
				if ($item->getItemType() !== TListItemType::EditItem) {
535
					$item->setItemType(TListItemType::SelectedItem);
536
				}
537
			}
538
		}
539
	}
540
541
	/**
542
	 * @return TDataGridItem the edit item
543
	 */
544
	public function getEditItem()
545
	{
546
		$index = $this->getEditItemIndex();
547
		$items = $this->getItems();
548
		if ($index >= 0 && $index < $items->getCount()) {
549
			return $items->itemAt($index);
550
		} else {
551
			return null;
552
		}
553
	}
554
555
	/**
556
	 * @return int the zero-based index of the edit item in {@see getItems Items}.
557
	 * A value -1 means no item is in edit mode.
558
	 */
559
	public function getEditItemIndex()
560
	{
561
		return $this->getViewState('EditItemIndex', -1);
562
	}
563
564
	/**
565
	 * Edits an item by its index in {@see getItems Items}.
566
	 * Previously editting item will change to normal item state.
567
	 * If the index is less than 0, any existing edit item will be cleared up.
568
	 * @param int $value the edit item index
569
	 */
570
	public function setEditItemIndex($value)
571
	{
572
		if (($value = TPropertyValue::ensureInteger($value)) < 0) {
573
			$value = -1;
574
		}
575
		if (($current = $this->getEditItemIndex()) !== $value) {
576
			$this->setViewState('EditItemIndex', $value, -1);
577
			$items = $this->getItems();
578
			$itemCount = $items->getCount();
579
			if ($current >= 0 && $current < $itemCount) {
580
				$items->itemAt($current)->setItemType($current % 2 ? TListItemType::AlternatingItem : TListItemType::Item);
581
			}
582
			if ($value >= 0 && $value < $itemCount) {
583
				$items->itemAt($value)->setItemType(TListItemType::EditItem);
584
			}
585
		}
586
	}
587
588
	/**
589
	 * @return bool whether sorting is enabled. Defaults to false.
590
	 */
591
	public function getAllowSorting()
592
	{
593
		return $this->getViewState('AllowSorting', false);
594
	}
595
596
	/**
597
	 * @param bool $value whether sorting is enabled
598
	 */
599
	public function setAllowSorting($value)
600
	{
601
		$this->setViewState('AllowSorting', TPropertyValue::ensureBoolean($value), false);
602
	}
603
604
	/**
605
	 * @return bool whether datagrid columns should be automatically generated. Defaults to true.
606
	 */
607
	public function getAutoGenerateColumns()
608
	{
609
		return $this->getViewState('AutoGenerateColumns', true);
610
	}
611
612
	/**
613
	 * @param bool $value whether datagrid columns should be automatically generated
614
	 */
615
	public function setAutoGenerateColumns($value)
616
	{
617
		$this->setViewState('AutoGenerateColumns', TPropertyValue::ensureBoolean($value), true);
618
	}
619
620
	/**
621
	 * @return bool whether the header should be displayed. Defaults to true.
622
	 */
623
	public function getShowHeader()
624
	{
625
		return $this->getViewState('ShowHeader', true);
626
	}
627
628
	/**
629
	 * @param bool $value whether the header should be displayed
630
	 */
631
	public function setShowHeader($value)
632
	{
633
		$this->setViewState('ShowHeader', TPropertyValue::ensureBoolean($value), true);
634
	}
635
636
	/**
637
	 * @return bool whether the footer should be displayed. Defaults to false.
638
	 */
639
	public function getShowFooter()
640
	{
641
		return $this->getViewState('ShowFooter', false);
642
	}
643
644
	/**
645
	 * @param bool $value whether the footer should be displayed
646
	 */
647
	public function setShowFooter($value)
648
	{
649
		$this->setViewState('ShowFooter', TPropertyValue::ensureBoolean($value), false);
650
	}
651
652
	/**
653
	 * @return \Prado\Web\UI\ITemplate the template applied when no data is bound to the datagrid
654
	 */
655
	public function getEmptyTemplate()
656
	{
657
		return $this->_emptyTemplate;
658
	}
659
660
	/**
661
	 * @param \Prado\Web\UI\ITemplate $value the template applied when no data is bound to the datagrid
662
	 * @throws TInvalidDataTypeException if the input is not an {@see \Prado\Web\UI\ITemplate} or not null.
663
	 */
664
	public function setEmptyTemplate($value)
665
	{
666
		if ($value instanceof ITemplate || $value === null) {
0 ignored issues
show
introduced by
$value is always a sub-type of Prado\Web\UI\ITemplate.
Loading history...
667
			$this->_emptyTemplate = $value;
668
		} else {
669
			throw new TInvalidDataTypeException('datagrid_template_required', 'EmptyTemplate');
670
		}
671
	}
672
673
	/**
674
	 * This method overrides parent's implementation to handle
675
	 * {@see onItemCommand OnItemCommand} event which is bubbled from
676
	 * {@see \Prado\Web\UI\WebControls\TDataGridItem} child controls.
677
	 * If the event parameter is {@see \Prado\Web\UI\WebControls\TDataGridCommandEventParameter} and
678
	 * the command name is a recognized one, which includes 'select', 'edit',
679
	 * 'delete', 'update', and 'cancel' (case-insensitive), then a
680
	 * corresponding command event is also raised (such as {@see onEditCommand OnEditCommand}).
681
	 * This method should only be used by control developers.
682
	 * @param \Prado\Web\UI\TControl $sender the sender of the event
683
	 * @param \Prado\TEventParameter $param event parameter
684
	 * @return bool whether the event bubbling should stop here.
685
	 */
686
	public function bubbleEvent($sender, $param)
687
	{
688
		if ($param instanceof TDataGridCommandEventParameter) {
689
			$this->onItemCommand($param);
690
			$command = $param->getCommandName();
691
			if (strcasecmp($command, self::CMD_SELECT) === 0) {
692
				$this->setSelectedItemIndex($param->getItem()->getItemIndex());
693
				$this->onSelectedIndexChanged($param);
694
				return true;
695
			} elseif (strcasecmp($command, self::CMD_EDIT) === 0) {
696
				$this->onEditCommand($param);
697
				return true;
698
			} elseif (strcasecmp($command, self::CMD_DELETE) === 0) {
699
				$this->onDeleteCommand($param);
700
				return true;
701
			} elseif (strcasecmp($command, self::CMD_UPDATE) === 0) {
702
				$this->onUpdateCommand($param);
703
				return true;
704
			} elseif (strcasecmp($command, self::CMD_CANCEL) === 0) {
705
				$this->onCancelCommand($param);
706
				return true;
707
			} elseif (strcasecmp($command, self::CMD_SORT) === 0) {
708
				$this->onSortCommand(new TDataGridSortCommandEventParameter($sender, $param));
709
				return true;
710
			} elseif (strcasecmp($command, self::CMD_PAGE) === 0) {
711
				$p = $param->getCommandParameter();
712
				if (strcasecmp($p, self::CMD_PAGE_NEXT) === 0) {
713
					$pageIndex = $this->getCurrentPageIndex() + 1;
714
				} elseif (strcasecmp($p, self::CMD_PAGE_PREV) === 0) {
715
					$pageIndex = $this->getCurrentPageIndex() - 1;
716
				} elseif (strcasecmp($p, self::CMD_PAGE_FIRST) === 0) {
717
					$pageIndex = 0;
718
				} elseif (strcasecmp($p, self::CMD_PAGE_LAST) === 0) {
719
					$pageIndex = $this->getPageCount() - 1;
720
				} else {
721
					$pageIndex = TPropertyValue::ensureInteger($p) - 1;
722
				}
723
				$this->onPageIndexChanged(new TDataGridPageChangedEventParameter($sender, $pageIndex));
724
				return true;
725
			}
726
		}
727
		return false;
728
	}
729
730
	/**
731
	 * Raises <b>OnCancelCommand</b> event.
732
	 * This method is invoked when a button control raises <b>OnCommand</b> event
733
	 * with <b>cancel</b> command name.
734
	 * @param TDataGridCommandEventParameter $param event parameter
735
	 */
736
	public function onCancelCommand($param)
737
	{
738
		$this->raiseEvent('OnCancelCommand', $this, $param);
739
	}
740
741
	/**
742
	 * Raises <b>OnDeleteCommand</b> event.
743
	 * This method is invoked when a button control raises <b>OnCommand</b> event
744
	 * with <b>delete</b> command name.
745
	 * @param TDataGridCommandEventParameter $param event parameter
746
	 */
747
	public function onDeleteCommand($param)
748
	{
749
		$this->raiseEvent('OnDeleteCommand', $this, $param);
750
	}
751
752
	/**
753
	 * Raises <b>OnEditCommand</b> event.
754
	 * This method is invoked when a button control raises <b>OnCommand</b> event
755
	 * with <b>edit</b> command name.
756
	 * @param TDataGridCommandEventParameter $param event parameter
757
	 */
758
	public function onEditCommand($param)
759
	{
760
		$this->raiseEvent('OnEditCommand', $this, $param);
761
	}
762
763
	/**
764
	 * Raises <b>OnItemCommand</b> event.
765
	 * This method is invoked when a button control raises <b>OnCommand</b> event.
766
	 * @param TDataGridCommandEventParameter $param event parameter
767
	 */
768
	public function onItemCommand($param)
769
	{
770
		$this->raiseEvent('OnItemCommand', $this, $param);
771
	}
772
773
	/**
774
	 * Raises <b>OnSortCommand</b> event.
775
	 * This method is invoked when a button control raises <b>OnCommand</b> event
776
	 * with <b>sort</b> command name.
777
	 * @param TDataGridSortCommandEventParameter $param event parameter
778
	 */
779
	public function onSortCommand($param)
780
	{
781
		$this->raiseEvent('OnSortCommand', $this, $param);
782
	}
783
784
	/**
785
	 * Raises <b>OnUpdateCommand</b> event.
786
	 * This method is invoked when a button control raises <b>OnCommand</b> event
787
	 * with <b>update</b> command name.
788
	 * @param TDataGridCommandEventParameter $param event parameter
789
	 */
790
	public function onUpdateCommand($param)
791
	{
792
		$this->raiseEvent('OnUpdateCommand', $this, $param);
793
	}
794
795
	/**
796
	 * Raises <b>OnItemCreated</b> event.
797
	 * This method is invoked right after a datagrid item is created and before
798
	 * added to page hierarchy.
799
	 * @param TDataGridItemEventParameter $param event parameter
800
	 */
801
	public function onItemCreated($param)
802
	{
803
		$this->raiseEvent('OnItemCreated', $this, $param);
804
	}
805
806
	/**
807
	 * Raises <b>OnPagerCreated</b> event.
808
	 * This method is invoked right after a datagrid pager is created and before
809
	 * added to page hierarchy.
810
	 * @param TDataGridPagerEventParameter $param event parameter
811
	 */
812
	public function onPagerCreated($param)
813
	{
814
		$this->raiseEvent('OnPagerCreated', $this, $param);
815
	}
816
817
	/**
818
	 * Raises <b>OnItemDataBound</b> event.
819
	 * This method is invoked for each datagrid item after it performs
820
	 * databinding.
821
	 * @param TDataGridItemEventParameter $param event parameter
822
	 */
823
	public function onItemDataBound($param)
824
	{
825
		$this->raiseEvent('OnItemDataBound', $this, $param);
826
	}
827
828
	/**
829
	 * Raises <b>OnPageIndexChanged</b> event.
830
	 * This method is invoked when current page is changed.
831
	 * @param TDataGridPageChangedEventParameter $param event parameter
832
	 */
833
	public function onPageIndexChanged($param)
834
	{
835
		$this->raiseEvent('OnPageIndexChanged', $this, $param);
836
	}
837
838
	/**
839
	 * Saves item count in viewstate.
840
	 * This method is invoked right before control state is to be saved.
841
	 */
842
	public function saveState()
843
	{
844
		parent::saveState();
845
		if (!$this->getEnableViewState(true)) {
846
			return;
847
		}
848
		if ($this->_items) {
849
			$this->setViewState('ItemCount', $this->_items->getCount(), 0);
850
		} else {
851
			$this->clearViewState('ItemCount');
852
		}
853
		if ($this->_autoColumns) {
854
			$state = [];
855
			foreach ($this->_autoColumns as $column) {
856
				$state[] = $column->saveState();
857
			}
858
			$this->setViewState('AutoColumns', $state, []);
859
		} else {
860
			$this->clearViewState('AutoColumns');
861
		}
862
		if ($this->_columns) {
863
			$state = [];
864
			foreach ($this->_columns as $column) {
865
				$state[] = $column->saveState();
866
			}
867
			$this->setViewState('Columns', $state, []);
868
		} else {
869
			$this->clearViewState('Columns');
870
		}
871
	}
872
873
	/**
874
	 * Loads item count information from viewstate.
875
	 * This method is invoked right after control state is loaded.
876
	 */
877
	public function loadState()
878
	{
879
		parent::loadState();
880
		if (!$this->getEnableViewState(true)) {
881
			return;
882
		}
883
		if (!$this->getIsDataBound()) {
884
			$state = $this->getViewState('AutoColumns', []);
885
			if (!empty($state)) {
886
				$this->_autoColumns = new TDataGridColumnCollection($this);
887
				foreach ($state as $st) {
888
					$columnClassName = $this->getAutoGenerateColumnName();
889
					$column = new $columnClassName();
890
					$column->loadState($st);
891
					$this->_autoColumns->add($column);
892
				}
893
			} else {
894
				$this->_autoColumns = null;
895
			}
896
			$state = $this->getViewState('Columns', []);
897
			if ($this->_columns && $this->_columns->getCount() === count($state)) {
898
				$i = 0;
899
				foreach ($this->_columns as $column) {
900
					$column->loadState($state[$i]);
901
					$i++;
902
				}
903
			}
904
			$this->restoreGridFromViewState();
905
		}
906
	}
907
908
	/**
909
	 * Clears up all items in the datagrid.
910
	 */
911
	public function reset()
912
	{
913
		$this->getControls()->clear();
914
		$this->getItems()->clear();
915
		$this->_header = null;
916
		$this->_footer = null;
917
		$this->_topPager = null;
918
		$this->_bottomPager = null;
919
		$this->_useEmptyTemplate = false;
920
	}
921
922
	/**
923
	 * Restores datagrid content from viewstate.
924
	 */
925
	protected function restoreGridFromViewState()
926
	{
927
		$this->reset();
928
929
		$allowPaging = $this->getAllowPaging();
930
931
		$itemCount = $this->getViewState('ItemCount', 0);
932
		$dsIndex = $this->getViewState('DataSourceIndex', 0);
933
934
		$columns = new TList($this->getColumns());
0 ignored issues
show
Bug introduced by
$this->getColumns() of type Prado\Web\UI\WebControls\TDataGridColumnCollection is incompatible with the type Iterator|array|null expected by parameter $data of Prado\Collections\TList::__construct(). ( Ignorable by Annotation )

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

934
		$columns = new TList(/** @scrutinizer ignore-type */ $this->getColumns());
Loading history...
935
		$columns->mergeWith($this->_autoColumns);
936
		$this->_allColumns = $columns;
937
938
		$items = $this->getItems();
939
940
		if ($columns->getCount()) {
941
			foreach ($columns as $column) {
942
				$column->initialize();
943
			}
944
			$selectedIndex = $this->getSelectedItemIndex();
945
			$editIndex = $this->getEditItemIndex();
946
			for ($index = 0; $index < $itemCount; ++$index) {
947
				if ($index === 0) {
948
					if ($allowPaging) {
949
						$this->_topPager = $this->createPager();
950
					}
951
					$this->_header = $this->createItemInternal(-1, -1, TListItemType::Header, false, null, $columns);
952
				}
953
				if ($index === $editIndex) {
954
					$itemType = TListItemType::EditItem;
955
				} elseif ($index === $selectedIndex) {
956
					$itemType = TListItemType::SelectedItem;
957
				} elseif ($index % 2) {
958
					$itemType = TListItemType::AlternatingItem;
959
				} else {
960
					$itemType = TListItemType::Item;
961
				}
962
				$items->add($this->createItemInternal($index, $dsIndex, $itemType, false, null, $columns));
963
				$dsIndex++;
964
			}
965
			if ($index > 0) {
966
				$this->_footer = $this->createItemInternal(-1, -1, TListItemType::Footer, false, null, $columns);
967
				if ($allowPaging) {
968
					$this->_bottomPager = $this->createPager();
969
				}
970
			}
971
		}
972
		if (!$dsIndex && $this->_emptyTemplate !== null) {
973
			$this->_useEmptyTemplate = true;
974
			$this->_emptyTemplate->instantiateIn($this);
975
		}
976
	}
977
978
	/**
979
	 * Performs databinding to populate datagrid items from data source.
980
	 * This method is invoked by {@see dataBind()}.
981
	 * You may override this function to provide your own way of data population.
982
	 * @param \Traversable $data the bound data
983
	 */
984
	protected function performDataBinding($data)
985
	{
986
		$this->reset();
987
		$keys = $this->getDataKeys();
988
		$keys->clear();
989
		$keyField = $this->getDataKeyField();
990
991
		// get all columns
992
		if ($this->getAutoGenerateColumns()) {
993
			$columns = new TList($this->getColumns());
0 ignored issues
show
Bug introduced by
$this->getColumns() of type Prado\Web\UI\WebControls\TDataGridColumnCollection is incompatible with the type Iterator|array|null expected by parameter $data of Prado\Collections\TList::__construct(). ( Ignorable by Annotation )

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

993
			$columns = new TList(/** @scrutinizer ignore-type */ $this->getColumns());
Loading history...
994
			$autoColumns = $this->createAutoColumns($data);
995
			$columns->mergeWith($autoColumns);
996
		} else {
997
			$columns = $this->getColumns();
998
		}
999
		$this->_allColumns = $columns;
1000
1001
		$items = $this->getItems();
1002
1003
		$index = 0;
1004
		$allowPaging = $this->getAllowPaging() && ($data instanceof TPagedDataSource);
1005
		$dsIndex = $allowPaging ? $data->getFirstIndexInPage() : 0;
1006
		$this->setViewState('DataSourceIndex', $dsIndex, 0);
1007
		if ($columns->getCount()) {
1008
			foreach ($columns as $column) {
1009
				$column->initialize();
1010
			}
1011
1012
			$selectedIndex = $this->getSelectedItemIndex();
1013
			$editIndex = $this->getEditItemIndex();
1014
			foreach ($data as $key => $row) {
1015
				if ($keyField !== '') {
1016
					$keys->add($this->getDataFieldValue($row, $keyField));
1017
				} else {
1018
					$keys->add($key);
1019
				}
1020
				if ($index === 0) {
1021
					if ($allowPaging) {
1022
						$this->_topPager = $this->createPager();
1023
					}
1024
					$this->_header = $this->createItemInternal(-1, -1, TListItemType::Header, true, null, $columns);
1025
				}
1026
				if ($index === $editIndex) {
1027
					$itemType = TListItemType::EditItem;
1028
				} elseif ($index === $selectedIndex) {
1029
					$itemType = TListItemType::SelectedItem;
1030
				} elseif ($index % 2) {
1031
					$itemType = TListItemType::AlternatingItem;
1032
				} else {
1033
					$itemType = TListItemType::Item;
1034
				}
1035
				$items->add($this->createItemInternal($index, $dsIndex, $itemType, true, $row, $columns));
1036
				$index++;
1037
				$dsIndex++;
1038
			}
1039
			if ($index > 0) {
1040
				$this->_footer = $this->createItemInternal(-1, -1, TListItemType::Footer, true, null, $columns);
1041
				if ($allowPaging) {
1042
					$this->_bottomPager = $this->createPager();
1043
				}
1044
			}
1045
		}
1046
		$this->setViewState('ItemCount', $index, 0);
1047
		if (!$dsIndex && $this->_emptyTemplate !== null) {
1048
			$this->_useEmptyTemplate = true;
1049
			$this->_emptyTemplate->instantiateIn($this);
1050
			$this->dataBindChildren();
1051
		}
1052
	}
1053
1054
	/**
1055
	 * Merges consecutive cells who have the same text.
1056
	 * @since 3.1.1
1057
	 */
1058
	private function groupCells()
1059
	{
1060
		if (($columns = $this->_allColumns) === null) {
1061
			return;
1062
		}
1063
		$items = $this->getItems();
1064
		foreach ($columns as $id => $column) {
1065
			if (!$column->getEnableCellGrouping()) {
1066
				continue;
1067
			}
1068
			$prevCell = null;
1069
			$prevCellText = null;
1070
			foreach ($items as $item) {
1071
				$itemType = $item->getItemType();
1072
				$cell = $item->getCells()->itemAt($id);
1073
				if (!$cell->getVisible()) {
1074
					continue;
1075
				}
1076
				if ($itemType === TListItemType::Item || $itemType === TListItemType::AlternatingItem || $itemType === TListItemType::SelectedItem) {
1077
					if (($cellText = $this->getCellText($cell)) === '') {
1078
						$prevCell = null;
1079
						$prevCellText = null;
1080
						continue;
1081
					}
1082
					if ($prevCell === null || $prevCellText !== $cellText) {
1083
						$prevCell = $cell;
1084
						$prevCellText = $cellText;
1085
					} else {
1086
						if (($rowSpan = $prevCell->getRowSpan()) === 0) {
0 ignored issues
show
Bug introduced by
The method getRowSpan() does not exist on null. ( Ignorable by Annotation )

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

1086
						if (($rowSpan = $prevCell->/** @scrutinizer ignore-call */ getRowSpan()) === 0) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1087
							$rowSpan = 1;
1088
						}
1089
						$prevCell->setRowSpan($rowSpan + 1);
1090
						$cell->setVisible(false);
1091
					}
1092
				}
1093
			}
1094
		}
1095
	}
1096
1097
	private function getCellText($cell)
1098
	{
1099
		if (($data = $cell->getText()) === '' && $cell->getHasControls()) {
1100
			$controls = $cell->getControls();
1101
			foreach ($controls as $control) {
1102
				if ($control instanceof \Prado\IDataRenderer) {
1103
					return $control->getData();
1104
				}
1105
			}
1106
		}
1107
		return $data;
1108
	}
1109
1110
	/**
1111
	 * Creates a datagrid item instance based on the item type and index.
1112
	 * @param int $itemIndex zero-based item index
1113
	 * @param mixed $dataSourceIndex
1114
	 * @param TListItemType $itemType item type
1115
	 * @return TDataGridItem created data list item
1116
	 */
1117
	protected function createItem($itemIndex, $dataSourceIndex, $itemType)
1118
	{
1119
		return new TDataGridItem($itemIndex, $dataSourceIndex, $itemType);
1120
	}
1121
1122
	private function createItemInternal($itemIndex, $dataSourceIndex, $itemType, $dataBind, $dataItem, $columns)
1123
	{
1124
		$item = $this->createItem($itemIndex, $dataSourceIndex, $itemType);
1125
		$this->initializeItem($item, $columns);
1126
		$param = new TDataGridItemEventParameter($item);
1127
		if ($dataBind) {
1128
			$item->setData($dataItem);
1129
			$this->onItemCreated($param);
1130
			$this->getControls()->add($item);
1131
			$item->dataBind();
1132
			$this->onItemDataBound($param);
1133
		} else {
1134
			$this->onItemCreated($param);
1135
			$this->getControls()->add($item);
1136
		}
1137
		return $item;
1138
	}
1139
1140
	/**
1141
	 * Initializes a datagrid item and cells inside it
1142
	 * @param TDataGridItem $item datagrid item to be initialized
1143
	 * @param TDataGridColumnCollection $columns datagrid columns to be used to initialize the cells in the item
1144
	 */
1145
	protected function initializeItem($item, $columns)
1146
	{
1147
		$cells = $item->getCells();
1148
		$itemType = $item->getItemType();
1149
		$index = 0;
1150
		foreach ($columns as $column) {
1151
			if ($itemType === TListItemType::Header) {
1152
				$cell = new TTableHeaderCell();
1153
			} else {
1154
				$cell = new TTableCell();
1155
			}
1156
			if (($id = $column->getID()) !== '') {
1157
				$item->registerObject($id, $cell);
1158
			}
1159
			$cells->add($cell);
1160
			$column->initializeCell($cell, $index, $itemType);
1161
			$index++;
1162
		}
1163
	}
1164
1165
	protected function createPager()
1166
	{
1167
		$pager = new TDataGridPager($this);
1168
		$this->buildPager($pager);
1169
		$this->onPagerCreated(new TDataGridPagerEventParameter($pager));
1170
		$this->getControls()->add($pager);
1171
		return $pager;
1172
	}
1173
1174
	/**
1175
	 * Builds the pager content based on pager style.
1176
	 * @param TDataGridPager $pager the container for the pager
1177
	 */
1178
	protected function buildPager($pager)
1179
	{
1180
		switch ($this->getPagerStyle()->getMode()) {
1181
			case TDataGridPagerMode::NextPrev:
1182
				$this->buildNextPrevPager($pager);
1183
				break;
1184
			case TDataGridPagerMode::Numeric:
1185
				$this->buildNumericPager($pager);
1186
				break;
1187
		}
1188
	}
1189
1190
	/**
1191
	 * Creates a pager button.
1192
	 * Depending on the button type, a TLinkButton or a TButton may be created.
1193
	 * If it is enabled (clickable), its command name and parameter will also be set.
1194
	 * Derived classes may override this method to create additional types of buttons, such as TImageButton.
1195
	 * @param mixed $pager the container pager instance of TActiveDatagridPager
1196
	 * @param string $buttonType button type, either LinkButton or PushButton
1197
	 * @param bool $enabled whether the button should be enabled
1198
	 * @param string $text caption of the button
1199
	 * @param string $commandName CommandName corresponding to the OnCommand event of the button
1200
	 * @param string $commandParameter CommandParameter corresponding to the OnCommand event of the button
1201
	 * @return mixed the button instance
1202
	 */
1203
	protected function createPagerButton($pager, $buttonType, $enabled, $text, $commandName, $commandParameter)
1204
	{
1205
		if ($buttonType === TDataGridPagerButtonType::LinkButton) {
1206
			if ($enabled) {
1207
				$button = new TLinkButton();
1208
			} else {
1209
				$button = new TLabel();
1210
				$button->setText($text);
1211
				return $button;
1212
			}
1213
		} else {
1214
			$button = new TButton();
1215
			if (!$enabled) {
1216
				$button->setEnabled(false);
1217
			}
1218
		}
1219
		$button->setText($text);
1220
		$button->setCommandName($commandName);
1221
		$button->setCommandParameter($commandParameter);
1222
		$button->setCausesValidation(false);
1223
		return $button;
1224
	}
1225
1226
	/**
1227
	 * Builds a next-prev pager
1228
	 * @param TDataGridPager $pager the container for the pager
1229
	 */
1230
	protected function buildNextPrevPager($pager)
1231
	{
1232
		$style = $this->getPagerStyle();
1233
		$buttonType = $style->getButtonType();
1234
		$controls = $pager->getControls();
1235
		$currentPageIndex = $this->getCurrentPageIndex();
1236
		if ($currentPageIndex === 0) {
1237
			if (($text = $style->getFirstPageText()) !== '') {
1238
				$label = $this->createPagerButton($pager, $buttonType, false, $text, '', '');
0 ignored issues
show
Bug introduced by
$buttonType of type Prado\Web\UI\WebControls\TDataGridPagerButtonType is incompatible with the type string expected by parameter $buttonType of Prado\Web\UI\WebControls...id::createPagerButton(). ( Ignorable by Annotation )

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

1238
				$label = $this->createPagerButton($pager, /** @scrutinizer ignore-type */ $buttonType, false, $text, '', '');
Loading history...
1239
				$controls->add($label);
1240
				$controls->add("\n");
1241
			}
1242
1243
			$label = $this->createPagerButton($pager, $buttonType, false, $style->getPrevPageText(), '', '');
1244
			$controls->add($label);
1245
		} else {
1246
			if (($text = $style->getFirstPageText()) !== '') {
1247
				$button = $this->createPagerButton($pager, $buttonType, true, $text, self::CMD_PAGE, self::CMD_PAGE_FIRST);
1248
				$controls->add($button);
1249
				$controls->add("\n");
1250
			}
1251
1252
			$button = $this->createPagerButton($pager, $buttonType, true, $style->getPrevPageText(), self::CMD_PAGE, self::CMD_PAGE_PREV);
1253
			$controls->add($button);
1254
		}
1255
		$controls->add("\n");
1256
		if ($currentPageIndex === $this->getPageCount() - 1) {
1257
			$label = $this->createPagerButton($pager, $buttonType, false, $style->getNextPageText(), '', '');
1258
			$controls->add($label);
1259
			if (($text = $style->getLastPageText()) !== '') {
1260
				$controls->add("\n");
1261
				$label = $this->createPagerButton($pager, $buttonType, false, $text, '', '');
1262
				$controls->add($label);
1263
			}
1264
		} else {
1265
			$button = $this->createPagerButton($pager, $buttonType, true, $style->getNextPageText(), self::CMD_PAGE, self::CMD_PAGE_NEXT);
1266
			$controls->add($button);
1267
			if (($text = $style->getLastPageText()) !== '') {
1268
				$controls->add("\n");
1269
				$button = $this->createPagerButton($pager, $buttonType, true, $text, self::CMD_PAGE, self::CMD_PAGE_LAST);
1270
				$controls->add($button);
1271
			}
1272
		}
1273
	}
1274
1275
	/**
1276
	 * Builds a numeric pager
1277
	 * @param TDataGridPager $pager the container for the pager
1278
	 */
1279
	protected function buildNumericPager($pager)
1280
	{
1281
		$style = $this->getPagerStyle();
1282
		$buttonType = $style->getButtonType();
1283
		$controls = $pager->getControls();
1284
		$pageCount = $this->getPageCount();
1285
		$pageIndex = $this->getCurrentPageIndex() + 1;
1286
		$maxButtonCount = $style->getPageButtonCount();
1287
		$buttonCount = $maxButtonCount > $pageCount ? $pageCount : $maxButtonCount;
1288
		$startPageIndex = 1;
1289
		$endPageIndex = $buttonCount;
1290
		if ($pageIndex > $endPageIndex) {
1291
			$startPageIndex = ((int) (($pageIndex - 1) / $maxButtonCount)) * $maxButtonCount + 1;
1292
			if (($endPageIndex = $startPageIndex + $maxButtonCount - 1) > $pageCount) {
1293
				$endPageIndex = $pageCount;
1294
			}
1295
			if ($endPageIndex - $startPageIndex + 1 < $maxButtonCount) {
1296
				if (($startPageIndex = $endPageIndex - $maxButtonCount + 1) < 1) {
1297
					$startPageIndex = 1;
1298
				}
1299
			}
1300
		}
1301
1302
		if ($startPageIndex > 1) {
1303
			if (($text = $style->getFirstPageText()) !== '') {
1304
				$button = $this->createPagerButton($pager, $buttonType, true, $text, self::CMD_PAGE, self::CMD_PAGE_FIRST);
0 ignored issues
show
Bug introduced by
$buttonType of type Prado\Web\UI\WebControls\TDataGridPagerButtonType is incompatible with the type string expected by parameter $buttonType of Prado\Web\UI\WebControls...id::createPagerButton(). ( Ignorable by Annotation )

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

1304
				$button = $this->createPagerButton($pager, /** @scrutinizer ignore-type */ $buttonType, true, $text, self::CMD_PAGE, self::CMD_PAGE_FIRST);
Loading history...
1305
				$controls->add($button);
1306
				$controls->add("\n");
1307
			}
1308
			$prevPageIndex = $startPageIndex - 1;
1309
			$button = $this->createPagerButton($pager, $buttonType, true, $style->getPrevPageText(), self::CMD_PAGE, "$prevPageIndex");
1310
			$controls->add($button);
1311
			$controls->add("\n");
1312
		}
1313
1314
		for ($i = $startPageIndex; $i <= $endPageIndex; ++$i) {
1315
			if ($i === $pageIndex) {
1316
				$label = $this->createPagerButton($pager, $buttonType, false, "$i", '', '');
1317
				$controls->add($label);
1318
			} else {
1319
				$button = $this->createPagerButton($pager, $buttonType, true, "$i", self::CMD_PAGE, "$i");
1320
				$controls->add($button);
1321
			}
1322
			if ($i < $endPageIndex) {
1323
				$controls->add("\n");
1324
			}
1325
		}
1326
1327
		if ($pageCount > $endPageIndex) {
1328
			$controls->add("\n");
1329
			$nextPageIndex = $endPageIndex + 1;
1330
			$button = $this->createPagerButton($pager, $buttonType, true, $style->getNextPageText(), self::CMD_PAGE, "$nextPageIndex");
1331
			$controls->add($button);
1332
			if (($text = $style->getLastPageText()) !== '') {
1333
				$controls->add("\n");
1334
				$button = $this->createPagerButton($pager, $buttonType, true, $text, self::CMD_PAGE, self::CMD_PAGE_LAST);
1335
				$controls->add($button);
1336
			}
1337
		}
1338
	}
1339
1340
	/**
1341
	 * Automatically generates datagrid columns based on datasource schema
1342
	 * @param \Traversable $dataSource data source bound to the datagrid
1343
	 * @return TDataGridColumnCollection
1344
	 */
1345
	protected function createAutoColumns($dataSource)
1346
	{
1347
		if (!$dataSource) {
0 ignored issues
show
introduced by
$dataSource is of type Traversable, thus it always evaluated to true.
Loading history...
1348
			return null;
1349
		}
1350
		$autoColumns = $this->getAutoColumns();
1351
		$autoColumns->clear();
1352
		$columnClassName = $this->getAutoGenerateColumnName();
1353
		foreach ($dataSource as $row) {
1354
			foreach ($row as $key => $value) {
1355
				$column = new $columnClassName();
1356
				if (is_string($key)) {
1357
					$column->setHeaderText($key);
1358
					$column->setDataField($key);
1359
					$column->setSortExpression($key);
1360
					$autoColumns->add($column);
1361
				} else {
1362
					$column->setHeaderText(TListItemType::Item);
1363
					$column->setDataField($key);
1364
					$column->setSortExpression(TListItemType::Item);
1365
					$autoColumns->add($column);
1366
				}
1367
			}
1368
			break;
1369
		}
1370
		return $autoColumns;
1371
	}
1372
1373
	/**
1374
	 * Applies styles to items, header, footer and separators.
1375
	 * Item styles are applied in a hierarchical way. Style in higher hierarchy
1376
	 * will inherit from styles in lower hierarchy.
1377
	 * Starting from the lowest hierarchy, the item styles include
1378
	 * item's own style, {@see getItemStyle ItemStyle}, {@see getAlternatingItemStyle AlternatingItemStyle},
1379
	 * {@see getSelectedItemStyle SelectedItemStyle}, and {@see getEditItemStyle EditItemStyle}.
1380
	 * Therefore, if background color is set as red in {@see getItemStyle ItemStyle},
1381
	 * {@see getEditItemStyle EditItemStyle} will also have red background color
1382
	 * unless it is set to a different value explicitly.
1383
	 */
1384
	protected function applyItemStyles()
1385
	{
1386
		$itemStyle = $this->getViewState('ItemStyle', null);
1387
1388
		$alternatingItemStyle = $this->getViewState('AlternatingItemStyle', null);
1389
		if ($itemStyle !== null) {
1390
			if ($alternatingItemStyle === null) {
1391
				$alternatingItemStyle = $itemStyle;
1392
			} else {
1393
				$alternatingItemStyle->mergeWith($itemStyle);
1394
			}
1395
		}
1396
1397
		$selectedItemStyle = $this->getViewState('SelectedItemStyle', null);
1398
1399
		$editItemStyle = $this->getViewState('EditItemStyle', null);
1400
		if ($selectedItemStyle !== null) {
1401
			if ($editItemStyle === null) {
1402
				$editItemStyle = $selectedItemStyle;
1403
			} else {
1404
				$editItemStyle->mergeWith($selectedItemStyle);
1405
			}
1406
		}
1407
1408
		$headerStyle = $this->getViewState('HeaderStyle', null);
1409
		$footerStyle = $this->getViewState('FooterStyle', null);
1410
		$pagerStyle = $this->getViewState('PagerStyle', null);
1411
		$separatorStyle = $this->getViewState('SeparatorStyle', null);
1412
1413
		foreach ($this->getControls() as $index => $item) {
1414
			if (!($item instanceof TDataGridItem) && !($item instanceof TDataGridPager)) {
1415
				continue;
1416
			}
1417
			$itemType = $item->getItemType();
1418
			switch ($itemType) {
1419
				case TListItemType::Header:
1420
					if ($headerStyle) {
1421
						$item->getStyle()->mergeWith($headerStyle);
1422
					}
1423
					if (!$this->getShowHeader()) {
1424
						$item->setVisible(false);
1425
					}
1426
					break;
1427
				case TListItemType::Footer:
1428
					if ($footerStyle) {
1429
						$item->getStyle()->mergeWith($footerStyle);
1430
					}
1431
					if (!$this->getShowFooter()) {
1432
						$item->setVisible(false);
1433
					}
1434
					break;
1435
				case TListItemType::Separator:
1436
					if ($separatorStyle) {
1437
						$item->getStyle()->mergeWith($separatorStyle);
1438
					}
1439
					break;
1440
				case TListItemType::Item:
1441
					if ($itemStyle) {
1442
						$item->getStyle()->mergeWith($itemStyle);
1443
					}
1444
					break;
1445
				case TListItemType::AlternatingItem:
1446
					if ($alternatingItemStyle) {
1447
						$item->getStyle()->mergeWith($alternatingItemStyle);
1448
					}
1449
					break;
1450
				case TListItemType::SelectedItem:
1451
					if ($selectedItemStyle) {
1452
						$item->getStyle()->mergeWith($selectedItemStyle);
1453
					}
1454
					if ($index % 2 == 1) {
1455
						if ($itemStyle) {
1456
							$item->getStyle()->mergeWith($itemStyle);
1457
						}
1458
					} else {
1459
						if ($alternatingItemStyle) {
1460
							$item->getStyle()->mergeWith($alternatingItemStyle);
1461
						}
1462
					}
1463
					break;
1464
				case TListItemType::EditItem:
1465
					if ($editItemStyle) {
1466
						$item->getStyle()->mergeWith($editItemStyle);
1467
					}
1468
					if ($index % 2 == 1) {
1469
						if ($itemStyle) {
1470
							$item->getStyle()->mergeWith($itemStyle);
1471
						}
1472
					} else {
1473
						if ($alternatingItemStyle) {
1474
							$item->getStyle()->mergeWith($alternatingItemStyle);
1475
						}
1476
					}
1477
					break;
1478
				case TListItemType::Pager:
1479
					if ($pagerStyle) {
1480
						$item->getStyle()->mergeWith($pagerStyle);
1481
						if ($index === 0) {
1482
							if ($pagerStyle->getPosition() === TDataGridPagerPosition::Bottom || !$pagerStyle->getVisible()) {
1483
								$item->setVisible(false);
1484
							}
1485
						} else {
1486
							if ($pagerStyle->getPosition() === TDataGridPagerPosition::Top || !$pagerStyle->getVisible()) {
1487
								$item->setVisible(false);
1488
							}
1489
						}
1490
					}
1491
					break;
1492
				default:
1493
					break;
1494
			}
1495
			if ($this->_columns && $itemType !== TListItemType::Pager) {
1496
				$n = $this->_columns->getCount();
1497
				$cells = $item->getCells();
0 ignored issues
show
Bug introduced by
The method getCells() does not exist on Prado\Web\UI\WebControls\TDataGridPager. 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

1497
				/** @scrutinizer ignore-call */ 
1498
    $cells = $item->getCells();
Loading history...
1498
				for ($i = 0; $i < $n; ++$i) {
1499
					$cell = $cells->itemAt($i);
1500
					$column = $this->_columns->itemAt($i);
1501
					if (!$column->getVisible()) {
1502
						$cell->setVisible(false);
1503
					} else {
1504
						if ($itemType === TListItemType::Header) {
1505
							$style = $column->getHeaderStyle(false);
1506
						} elseif ($itemType === TListItemType::Footer) {
1507
							$style = $column->getFooterStyle(false);
1508
						} else {
1509
							$style = $column->getItemStyle(false);
1510
						}
1511
						if ($style !== null) {
1512
							$cell->getStyle()->mergeWith($style);
1513
						}
1514
					}
1515
				}
1516
			}
1517
		}
1518
	}
1519
1520
	/**
1521
	 * Renders the openning tag for the datagrid control which will render table caption if present.
1522
	 * @param \Prado\Web\UI\THtmlWriter $writer the writer used for the rendering purpose
1523
	 */
1524
	public function renderBeginTag($writer)
1525
	{
1526
		parent::renderBeginTag($writer);
1527
		if (($caption = $this->getCaption()) !== '') {
1528
			if (($align = $this->getCaptionAlign()) !== TTableCaptionAlign::NotSet) {
0 ignored issues
show
introduced by
The condition $align = $this->getCapti...bleCaptionAlign::NotSet is always true.
Loading history...
1529
				$writer->addStyleAttribute('caption-side', strtolower($align));
0 ignored issues
show
Bug introduced by
$align of type Prado\Web\UI\WebControls\TTableCaptionAlign is incompatible with the type string expected by parameter $string of strtolower(). ( Ignorable by Annotation )

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

1529
				$writer->addStyleAttribute('caption-side', strtolower(/** @scrutinizer ignore-type */ $align));
Loading history...
1530
			}
1531
			$writer->renderBeginTag('caption');
1532
			$writer->write($caption);
1533
			$writer->renderEndTag();
1534
		}
1535
	}
1536
1537
	/**
1538
	 * Renders the datagrid.
1539
	 * @param \Prado\Web\UI\THtmlWriter $writer writer for the rendering purpose
1540
	 */
1541
	public function render($writer)
1542
	{
1543
		if ($this->getHasControls()) {
1544
			$this->groupCells();
1545
			if ($this->_useEmptyTemplate) {
1546
				$control = new TWebControl();
1547
				$control->setID($this->getClientID());
1548
				$control->copyBaseAttributes($this);
1549
				if ($this->getHasStyle()) {
1550
					$control->getStyle()->copyFrom($this->getStyle());
1551
				}
1552
				$control->renderBeginTag($writer);
1553
				$this->renderContents($writer);
1554
				$control->renderEndTag($writer);
1555
			} elseif ($this->getViewState('ItemCount', 0) > 0) {
1556
				$this->applyItemStyles();
1557
				if ($this->_topPager) {
1558
					$this->_topPager->renderControl($writer);
1559
					$writer->writeLine();
1560
				}
1561
				$this->renderTable($writer);
1562
				if ($this->_bottomPager) {
1563
					$writer->writeLine();
1564
					$this->_bottomPager->renderControl($writer);
1565
				}
1566
			}
1567
		}
1568
	}
1569
1570
	/**
1571
	 * Renders the tabular data.
1572
	 * @param \Prado\Web\UI\THtmlWriter $writer writer
1573
	 */
1574
	protected function renderTable($writer)
1575
	{
1576
		$this->renderBeginTag($writer);
1577
		if ($this->_header && $this->_header->getVisible()) {
1578
			$writer->writeLine();
1579
			if ($style = $this->getViewState('TableHeadStyle', null)) {
1580
				$style->addAttributesToRender($writer);
1581
			}
1582
			$writer->renderBeginTag('thead');
1583
			$this->_header->render($writer);
1584
			$writer->renderEndTag();
1585
		}
1586
		$writer->writeLine();
1587
		if ($style = $this->getViewState('TableBodyStyle', null)) {
1588
			$style->addAttributesToRender($writer);
1589
		}
1590
		$writer->renderBeginTag('tbody');
1591
		foreach ($this->getItems() as $item) {
1592
			$item->renderControl($writer);
1593
		}
1594
		$writer->renderEndTag();
1595
1596
		if ($this->_footer && $this->_footer->getVisible()) {
1597
			$writer->writeLine();
1598
			if ($style = $this->getViewState('TableFootStyle', null)) {
1599
				$style->addAttributesToRender($writer);
1600
			}
1601
			$writer->renderBeginTag('tfoot');
1602
			$this->_footer->render($writer);
1603
			$writer->renderEndTag();
1604
		}
1605
1606
		$writer->writeLine();
1607
		$this->renderEndTag($writer);
1608
	}
1609
}
1610