Completed
Pull Request — master (#435)
by
unknown
02:13
created

Column   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 719
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 8

Test Coverage

Coverage 24.06%

Importance

Changes 0
Metric Value
wmc 78
lcom 5
cbo 8
dl 0
loc 719
ccs 32
cts 133
cp 0.2406
rs 1.6901
c 0
b 0
f 0

49 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 23 3
A useRenderer() 0 18 4
A setTemplateEscaping() 0 6 1
A isTemplateEscaped() 0 4 1
A setHeaderEscaping() 0 6 1
A isHeaderEscaped() 0 4 1
A setSortable() 0 6 2
A isSortable() 0 4 1
A setSortableResetPagination() 0 6 1
A sortableResetPagination() 0 4 1
A setSortableCallback() 0 6 1
A getSortableCallback() 0 4 1
A getSortingColumn() 0 4 2
A getColumnName() 0 4 1
A getColumnValue() 0 4 1
A getName() 0 4 1
A setReplacement() 0 6 1
A hasReplacements() 0 4 1
A applyReplacements() 0 10 4
B setRenderer() 0 24 5
A setRendererOnCondition() 0 4 1
A getRenderer() 0 4 1
A setTemplate() 0 7 1
A getTemplateVariables() 0 4 1
A hasTemplate() 0 4 1
A getTemplate() 0 4 1
A isSortedBy() 0 4 1
A setSort() 0 6 1
A getSortNext() 0 10 3
A hasSortNext() 0 6 2
A isSortAsc() 0 4 1
A setAlign() 0 6 1
A hasAlign() 0 4 1
A getAlign() 0 4 2
A setFitContent() 0 6 2
A setEditableCallback() 0 6 1
A getEditableCallback() 0 4 1
A isEditable() 0 4 1
A setEditableInputType() 0 6 1
A setEditableInputTypeSelect() 0 14 2
A getEditableInputType() 0 4 1
A addAttributes() 0 7 1
C getElementPrototype() 0 36 7
A setDefaultHide() 0 10 2
A getDefaultHide() 0 4 1
A getItemParams() 0 10 3
A getColumn() 0 4 1
A setTranslatableHeader() 0 6 1
A isTranslatableHeader() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Column 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 Column, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid\Column;
10
11
use Ublaboo;
12
use Ublaboo\DataGrid\DataGrid;
13
use Ublaboo\DataGrid\Row;
14
use Ublaboo\DataGrid\Exception\DataGridException;
15
use Ublaboo\DataGrid\Exception\DataGridColumnRendererException;
16
use Nette\Utils\Html;
17
use Ublaboo\DataGrid\Traits;
18
19
abstract class Column extends FilterableColumn
20 1
{
21
22 1
	use Traits\TLink;
23
24
	/**
25
	 * @var array
26
	 */
27
	protected $replacements = [];
28
29
	/**
30
	 * @var Renderer|NULL
31
	 */
32
	protected $renderer;
33
34
	/**
35
	 * @var string
36
	 */
37
	protected $template;
38
39
	/**
40
	 * @var bool|string
41
	 */
42
	protected $sortable = FALSE;
43
	
44
	/**
45
	 * @var bool
46
	 */
47
	protected $translatable_header = TRUE;
48
49
	/**
50
	 * @var bool
51
	 */
52
	protected $sortable_reset_pagination = FALSE;
53
54
	/**
55
	 * @var null|callable
56
	 */
57
	protected $sortable_callback = NULL;
58
59
	/**
60
	 * @var array
61
	 */
62
	protected $sort;
63
64
	/**
65
	 * @var bool
66
	 */
67
	protected $template_escaping = TRUE;
68
69
	/**
70
	 * @var bool
71
	 */
72
	protected $header_escaping = FALSE;
73
74
	
75
	/**
76
	 * @var string
77
	 */
78
	protected $align;
79
80
81
	/**
82
	 * @var array
83
	 */
84
	protected $template_variables = [];
85
86
	/**
87
	 * @var callable
88
	 */
89
	protected $editable_callback;
90
91
	/**
92
	 * @var array
93
	 */
94
	protected $editable_element = ['textarea', ['class' => 'form-control']];
95
96
	/**
97
	 * @var bool
98
	 */
99
	protected $default_hide = FALSE;
100
101
102
	/**
103
	 * Render row item into template
104
	 * @param  Row   $row
105
	 * @return mixed
106
	 */
107
	public function render(Row $row)
108
	{
109
		/**
110
		 * Renderer function may be used
111
		 */
112
		try {
113 1
			return $this->useRenderer($row);
114 1
		} catch (DataGridColumnRendererException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
115
			/**
116
			 * Do not use renderer
117
			 */
118
		}
119
120
		/**
121
		 * Or replacements may be applied
122
		 */
123 1
		list($do_replace, $replaced) = $this->applyReplacements($row);
124 1
		if ($do_replace) {
125
			return $replaced;
126
		}
127
128 1
		return $this->getColumnValue($row);
129
	}
130
131
132
	/**
133
	 * Try to render item with custom renderer
134
	 * @param  Row   $row
135
	 * @return mixed
136
	 */
137
	public function useRenderer(Row $row)
138
	{
139 1
		$renderer = $this->getRenderer();
140
141 1
		if (!$renderer) {
142 1
			throw new DataGridColumnRendererException;
143
		}
144
145
		if ($renderer->getConditionCallback()) {
146
			if (!call_user_func_array($renderer->getConditionCallback(), [$row->getItem()])) {
147
				throw new DataGridColumnRendererException;
148
			}
149
150
			return call_user_func_array($renderer->getCallback(), [$row->getItem()]);
151
		}
152
153
		return call_user_func_array($renderer->getCallback(), [$row->getItem()]);
154
	}
155
156
157
	/**
158
	 * Should be column values escaped in latte?
159
	 * @param bool $template_escaping
160
	 */
161
	public function setTemplateEscaping($template_escaping = TRUE)
162
	{
163
		$this->template_escaping = (bool) $template_escaping;
164
165
		return $this;
166
	}
167
168
169
	public function isTemplateEscaped()
170
	{
171 1
		return $this->template_escaping;
172
	}
173
174
	/**
175
	 * Should be column header escaped in latte?
176
	 * @param bool $header_escaping
177
	 */
178
	public function setHeaderEscaping($header_escaping = FALSE)
179
	{
180
		$this->header_escaping = (bool) $header_escaping;
181
182
		return $this;
183
	}
184
	
185
	public function isHeaderEscaped()
186
	{
187
		return $this->header_escaping;
188
	}
189
	
190
	
191
	/**
192
	 * Set column sortable or not
193
	 * @param bool|string $sortable
194
	 */
195
	public function setSortable($sortable = TRUE)
196
	{
197 1
		$this->sortable = is_string($sortable) ? $sortable : (bool) $sortable;
198
199 1
		return $this;
200
	}
201
202
203
	/**
204
	 * Tell whether column is sortable
205
	 * @return bool
206
	 */
207
	public function isSortable()
208
	{
209 1
		return (bool) $this->sortable;
210
	}
211
	
212
	/**
213
	 * Set column translatable or not
214
	 */
215
	public function setTranslatableHeader($translatable_header = TRUE)
216
	{
217 1
		$this->translatable_header = (bool) $translatable_header;
218
		
219 1
		return $this;
220
	}
221
	
222
	/**
223
	 * Tell wheter column is translatable
224
	 */
225
	public function isTranslatableHeader()
226
	{
227 1
		return (bool) $this->translatable_header;
228
	}
229
230
	/**
231
	 * Shoud be the pagination reseted after sorting?
232
	 * @param bool $sortable_reset_pagination
233
	 * @return static
234
	 */
235
	public function setSortableResetPagination($sortable_reset_pagination = TRUE)
236
	{
237
		$this->sortable_reset_pagination = (bool) $sortable_reset_pagination;
238
239
		return $this;
240
	}
241
242
243
	/**
244
	 * Do reset pagination after sorting?
245
	 * @return bool
246
	 */
247
	public function sortableResetPagination()
248
	{
249
		return $this->sortable_reset_pagination;
250
	}
251
252
253
	/**
254
	 * Set custom ORDER BY clause
255
	 * @param callable $sortable_callback
256
	 * @return static
257
	 */
258
	public function setSortableCallback(callable $sortable_callback)
259
	{
260
		$this->sortable_callback = $sortable_callback;
261
262
		return $this;
263
	}
264
265
266
	/**
267
	 * Get custom ORDER BY clause
268
	 * @return callable|null
269
	 */
270
	public function getSortableCallback()
271
	{
272
		return $this->sortable_callback;
273
	}
274
275
276
	/**
277
	 * Get column to sort by
278
	 * @return string
279
	 */
280
	public function getSortingColumn()
281
	{
282
		return is_string($this->sortable) ? $this->sortable : $this->column;
283
	}
284
285
286
	/**
287
	 * Get column name
288
	 * @return string
289
	 */
290
	public function getColumnName()
291
	{
292
		return $this->column;
293
	}
294
295
296
	/**
297
	 * Get column value of row item
298
	 * @param  Row   $row
299
	 * @return mixed
300
	 */
301
	public function getColumnValue(Row $row)
302
	{
303 1
		return $row->getValue($this->column);
304
	}
305
306
307
	/**
308
	 * @return string
309
	 */
310
	public function getName()
311
	{
312 1
		return $this->name;
313
	}
314
315
316
	/**
317
	 * Set column replacements
318
	 * @param  array $replacements
319
	 * @return Column
320
	 */
321
	public function setReplacement(array $replacements)
322
	{
323
		$this->replacements = $replacements;
324
325
		return $this;
326
	}
327
328
329
	/**
330
	 * Tell whether columns has replacements
331
	 * @return bool
332
	 */
333
	public function hasReplacements()
334
	{
335
		return (bool) $this->replacements;
336
	}
337
338
339
	/**
340
	 * Apply replacements
341
	 * @param  Row   $row
342
	 * @return array
343
	 */
344
	public function applyReplacements(Row $row)
345
	{
346 1
		$value = $row->getValue($this->column);
347
348 1
		if ((is_scalar($value) || is_null($value)) && isset($this->replacements[$value])) {
349
			return [TRUE, $this->replacements[$value]];
350
		}
351
352 1
		return [FALSE, NULL];
353
	}
354
355
356
	/**
357
	 * Set renderer callback and (it may be optional - the condition callback will decide)
358
	 * @param callable $renderer
359
	 */
360
	public function setRenderer($renderer, $condition_callback = NULL)
361
	{
362
		if ($this->hasReplacements()) {
363
			throw new DataGridException(
364
				"Use either Column::setReplacement() or Column::setRenderer, not both."
365
			);
366
		}
367
368
		if (!is_callable($renderer)) {
369
			throw new DataGridException(
370
				"Renderer (method Column::setRenderer()) must be callable."
371
			);
372
		}
373
374
		if (NULL != $condition_callback && !is_callable($condition_callback)) {
375
			throw new DataGridException(
376
				"Renderer (method Column::setRenderer()) must be callable."
377
			);
378
		}
379
380
		$this->renderer = new Renderer($renderer, $condition_callback);
381
382
		return $this;
383
	}
384
385
386
	/**
387
	 * Set renderer callback just if condition is truthy
388
	 * @param callable $renderer
389
	 */
390
	public function setRendererOnCondition($renderer, $condition_callback)
391
	{
392
		return $this->setRenderer($renderer, $condition_callback);
393
	}
394
395
396
	/**
397
	 * Return custom renderer callback
398
	 * @return Renderer|null
399
	 */
400
	public function getRenderer()
401
	{
402 1
		return $this->renderer;
403
	}
404
405
406
	/**
407
	 * Column may have its own template
408
	 * @param string $template
409
	 */
410
	public function setTemplate($template, array $template_variables = [])
411
	{
412 1
		$this->template = $template;
413 1
		$this->template_variables = $template_variables;
414
415 1
		return $this;
416
	}
417
418
419
	/**
420
	 * Column can have variables that will be passed to custom template scope
421
	 * @return array
422
	 */
423
	public function getTemplateVariables()
424
	{
425
		return $this->template_variables;
426
	}
427
428
429
	/**
430
	 * Tell whether column has its owntemplate
431
	 * @return bool
432
	 */
433
	public function hasTemplate()
434
	{
435
		return (bool) $this->template;
436
	}
437
438
439
	/**
440
	 * Get column template path
441
	 * @return string
442
	 */
443
	public function getTemplate()
444
	{
445
		return $this->template;
446
	}
447
448
449
	/**
450
	 * Tell whether data source is sorted by this collumn
451
	 * @return bool
452
	 */
453
	public function isSortedBy()
454
	{
455
		return (bool) $this->sort;
456
	}
457
458
459
	/**
460
	 * Tell column his sorting options
461
	 * @param array $sort
462
	 */
463
	public function setSort(array $sort)
464
	{
465
		$this->sort = $sort[$this->key];
466
467
		return $this;
468
	}
469
470
471
	/**
472
	 * What sorting will be applied after next click?
473
	 * @return array
474
	 */
475
	public function getSortNext()
476
	{
477
		if ($this->sort == 'ASC') {
478
			return [$this->key => 'DESC'];
479
		} else if ($this->sort == 'DESC') {
480
			return [$this->key => FALSE];
481
		}
482
483
		return [$this->key => 'ASC'];
484
	}
485
486
487
	/**
488
	 * @return bool
489
	 */
490
	public function hasSortNext()
491
	{
492
		foreach ($this->getSortNext() as $key => $order) {
493
			return $order !== FALSE;
494
		}
495
	}
496
497
498
	/**
499
	 * Is sorting ascending?
500
	 * @return bool
501
	 */
502
	public function isSortAsc()
503
	{
504
		return $this->sort == 'ASC';
505
	}
506
507
508
	/**
509
	 * Set column alignment
510
	 * @param string $align
511
	 */
512
	public function setAlign($align)
513
	{
514
		$this->align = (string) $align;
515
516
		return $this;
517
	}
518
519
520
	/**
521
	 * Has column some alignment?
522
	 * @return bool [description]
523
	 */
524
	public function hasAlign()
525
	{
526
		return (bool) $this->align;
527
	}
528
529
530
	/**
531
	 * Get column alignment
532
	 * @return string
533
	 */
534
	public function getAlign()
535
	{
536
		return $this->align ?: 'left';
537
	}
538
539
540
	/**
541
	 * Set column content fit
542
	 * @param bool $fit_content
543
	 * @return $this
544
	 */
545
	public function setFitContent($fit_content = TRUE)
546
	{
547
		($fit_content) ? $this->addAttributes(['class' => 'datagrid-fit-content']) : NULL;
548
549
		return $this;
550
	}
551
552
553
	/**
554
	 * Set callback that will be called after inline editing
555
	 * @param callable $editable_callback
556
	 */
557
	public function setEditableCallback(callable $editable_callback)
558
	{
559
		$this->editable_callback = $editable_callback;
560
561
		return $this;
562
	}
563
564
565
	/**
566
	 * Return callback that is used after inline editing
567
	 * @return callable
568
	 */
569
	public function getEditableCallback()
570
	{
571
		return $this->editable_callback;
572
	}
573
574
575
	/**
576
	 * Is column editable?
577
	 * @return bool
578
	 */
579
	public function isEditable()
580
	{
581
		return (bool) $this->getEditableCallback();
582
	}
583
584
585
	/**
586
	 * Element is by default textarea, user can change that
587
	 * @param string $el_type
588
	 * @param array  $attrs
589
	 * @return static
590
	 */
591
	public function setEditableInputType($el_type, array $attrs = [])
592
	{
593
		$this->editable_element = [$el_type, $attrs];
594
595
		return $this;
596
	}
597
598
599
	/**
600
	 * Change small inline edit input type to select
601
	 * @param array  $options
602
	 * @return static
603
	 */
604
	public function setEditableInputTypeSelect(array $options = [])
605
	{
606
		$select = Html::el('select');
607
608
		foreach ($options as $value => $text) {
609
			$select->create('option')
610
				->value($value)
611
				->setText($text);
612
		}
613
614
		$this->addAttributes(['data-datagrid-editable-element' => (string) $select]);
615
616
		return $this->setEditableInputType('select');
617
	}
618
619
620
	/**
621
	 * [getEditableInputType description]
622
	 * @return array
623
	 */
624
	public function getEditableInputType()
625
	{
626
		return $this->editable_element;
627
	}
628
629
630
	/**
631
	 * Set attributes for both th and td element
632
	 * @param array $attrs
633
	 * @return static
634
	 */
635
	public function addAttributes(array $attrs)
636
	{
637
		$this->getElementPrototype('td')->addAttributes($attrs);
638
		$this->getElementPrototype('th')->addAttributes($attrs);
639
640
		return $this;
641
	}
642
643
644
	/**
645
	 * Get th/td column element
646
	 * @param  string   $tag th|td
647
	 * @param  string   $key
648
	 * @param  Row|NULL $row
649
	 * @return Html
650
	 */
651
	public function getElementPrototype($tag, $key = NULL, Row $row = NULL)
652
	{
653
		$el = Html::el($tag);
654
655
		/**
656
		 * If class was set by user via $el->class = '', fix it
657
		 */
658
		if (!empty($el->class) && is_string($el->class)) {
659
			$class = $el->class;
660
			unset($el->class);
661
662
			$el->class[] = $class;
663
		}
664
665
		$el->class[] = "text-{$this->getAlign()}";
666
667
		/**
668
		 * Method called from datagrid template, set appropriate classes and another attributes
669
		 */
670
		if ($key !== NULL && $row !== NULL) {
671
			$el->class[] = "col-{$key}";
672
673
			if ($tag == 'td') {
674
				if ($this->isEditable()) {
675
					$link = $this->grid->link('edit!', ['key' => $key, 'id' => $row->getId()]);
676
677
					$el->data('datagrid-editable-url', $link);
678
679
					$el->data('datagrid-editable-type', $this->editable_element[0]);
680
					$el->data('datagrid-editable-attrs', json_encode($this->editable_element[1]));
681
				}
682
			}
683
		}
684
685
		return $el;
686
	}
687
688
689
	/**
690
	 * @param bool $default_hide
691
	 * @return static
692
	 */
693
	public function setDefaultHide($default_hide = TRUE)
694
	{
695
		$this->default_hide = (bool) $default_hide;
696
697
		if ($default_hide) {
698
			$this->grid->setSomeColumnDefaultHide($default_hide);
699
		}
700
701
		return $this;
702
	}
703
704
705
	public function getDefaultHide()
706
	{
707
		return $this->default_hide;
708
	}
709
710
711
	/**
712
	 * Get row item params (E.g. action may be called id => $item->id, name => $item->name, ...)
713
	 * @param  Row   $row
714
	 * @param  array $params_list
715
	 * @return array
716
	 */
717
	protected function getItemParams(Row $row, array $params_list)
718
	{
719 1
		$return = [];
720
721 1
		foreach ($params_list as $param_name => $param) {
722 1
			$return[is_string($param_name) ? $param_name : $param] = $row->getValue($param);
723 1
		}
724
725 1
		return $return;
726
	}
727
728
729
	/**
730
	 * @return string
731
	 */
732
	public function getColumn()
733
	{
734
		return $this->column;
735
	}
736
737
}
738