Completed
Push — master ( 9db12b...087db8 )
by Jean-Christophe
02:56
created

DataTable::_generateRow()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 3
eloc 20
nc 3
nop 3
1
<?php
2
3
namespace Ajax\semantic\widgets\datatable;
4
5
use Ajax\common\Widget;
6
use Ajax\JsUtils;
7
use Ajax\semantic\html\collections\table\HtmlTable;
8
use Ajax\semantic\html\elements\HtmlInput;
9
use Ajax\semantic\html\collections\menus\HtmlPaginationMenu;
10
use Ajax\semantic\html\modules\checkbox\HtmlCheckbox;
11
use Ajax\semantic\html\base\constants\Direction;
12
use Ajax\service\JArray;
13
use Ajax\semantic\widgets\base\InstanceViewer;
14
use Ajax\semantic\html\collections\table\traits\TableTrait;
15
use Ajax\semantic\html\collections\HtmlMessage;
16
use Ajax\semantic\html\collections\menus\HtmlMenu;
17
use Ajax\semantic\html\base\traits\BaseTrait;
18
19
/**
20
 * DataTable widget for displaying list of objects
21
 * @version 1.0
22
 * @author jc
23
 * @since 2.2
24
 *
25
 */
26
class DataTable extends Widget {
27
	use TableTrait,DataTableFieldAsTrait,HasCheckboxesTrait,BaseTrait;
28
	protected $_searchField;
29
	protected $_urls;
30
	protected $_pagination;
31
	protected $_compileParts;
32
	protected $_deleteBehavior;
33
	protected $_editBehavior;
34
	protected $_visibleHover=false;
35
	protected $_targetSelector;
36
	protected $_refreshSelector;
37
	protected $_emptyMessage;
38
	protected $_json;
39
	protected $_rowClass="";
40
	protected $_sortable;
41
	protected $_hiddenColumns;
42
	protected $_colWidths;
43
44
45
	public function __construct($identifier,$model,$modelInstance=NULL) {
46
		parent::__construct($identifier, $model,$modelInstance);
47
		$this->_init(new InstanceViewer($identifier), "table", new HtmlTable($identifier, 0,0), false);
48
		$this->_urls=[];
49
		$this->_emptyMessage=new HtmlMessage("","nothing to display");
50
		$this->_emptyMessage->setIcon("info circle");
51
	}
52
53
	public function run(JsUtils $js){
54
		if($this->_hasCheckboxes && isset($js)){
55
			$this->_runCheckboxes($js);
56
		}
57
		if($this->_visibleHover){
58
			$js->execOn("mouseover", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'visible');",["preventDefault"=>false,"stopPropagation"=>true]);
59
			$js->execOn("mouseout", "#".$this->identifier." tr", "$(event.target).closest('tr').find('.visibleover').css('visibility', 'hidden');",["preventDefault"=>false,"stopPropagation"=>true]);
60
		}
61
		if(\is_array($this->_deleteBehavior))
62
			$this->_generateBehavior("delete",$this->_deleteBehavior, $js);
63
		if(\is_array($this->_editBehavior))
64
			$this->_generateBehavior("edit",$this->_editBehavior,$js);
65
		return parent::run($js);
66
	}
67
68
69
70
	protected function _generateBehavior($op,$params,JsUtils $js){
71
		if(isset($this->_urls[$op])){
72
			$params=\array_merge($params,["attr"=>"data-ajax"]);
73
			$js->ajaxOnClick("#".$this->identifier." ._".$op, $this->_urls[$op],$this->getTargetSelector($op),$params);
74
		}
75
	}
76
77
	/**
78
	 * {@inheritDoc}
79
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
80
	 */
81
	protected function getTable() {
82
		return $this->content["table"];
83
	}
84
85
86
	public function compile(JsUtils $js=NULL,&$view=NULL){
87
		if(!$this->_generated){
88
			$this->_instanceViewer->setInstance($this->_model);
89
			$captions=$this->_instanceViewer->getCaptions();
90
			$table=$this->content["table"];
91
			if($this->_hasCheckboxes){
92
				$this->_generateMainCheckbox($captions);
93
			}
94
			$table->setRowCount(0, \sizeof($captions));
95
			$this->_generateHeader($table,$captions);
96
97
			if(isset($this->_compileParts))
98
				$table->setCompileParts($this->_compileParts);
99
100
			$this->_generateContent($table);
101
102
			$this->compileExtraElements($table, $captions,$js);
103
104
			$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
105
			$this->_compileForm();
106
			$this->_applyStyleAttributes($table);
107
			$this->_generated=true;
108
		}
109
		return parent::compile($js,$view);
110
	}
111
112
	protected function compileExtraElements($table,$captions,JsUtils $js=NULL){
113
		if(isset($this->_searchField) && isset($js) && isset($this->_urls["refresh"])){
114
				$this->_searchField->postOn("change", $this->_urls["refresh"],"{'s':$(this).val()}","#".$this->identifier." tbody",["preventDefault"=>false,"jqueryDone"=>"replaceWith"]);
115
		}
116
		if($this->_hasCheckboxes && $table->hasPart("thead")){
117
			$table->getHeader()->getCell(0, 0)->addClass("no-sort");
118
		}
119
120
		if(isset($this->_toolbar)){
121
			$this->_setToolbarPosition($table, $captions);
122
		}
123
		if(isset($this->_pagination) && $this->_pagination->getVisible()){
124
			$this->_generatePagination($table,$js);
125
		}
126
	}
127
128
	protected function _applyStyleAttributes($table){
129
		if(isset($this->_hiddenColumns))
130
			$this->_hideColumns();
131
			if(isset($this->_colWidths)){
132
				foreach ($this->_colWidths as $colIndex=>$width){
133
					$table->setColWidth($colIndex,$width);
134
				}
135
			}
136
	}
137
138
	protected function _hideColumns(){
139
		foreach ($this->_hiddenColumns as $colIndex){
140
			$this->_self->hideColumn($colIndex);
141
		}
142
		return $this;
143
	}
144
145
	protected function _generateHeader(HtmlTable $table,$captions){
146
		$table->setHeaderValues($captions);
147
		if(isset($this->_sortable)){
148
			$table->setSortable($this->_sortable);
149
		}
150
	}
151
152
153
154
	protected function _generateContent($table){
155
		$objects=$this->_modelInstance;
156
		if(isset($this->_pagination)){
157
			$objects=$this->_pagination->getObjects($this->_modelInstance);
158
		}
159
			InstanceViewer::setIndex(0);
160
			$table->fromDatabaseObjects($objects, function($instance) use($table){
161
				return $this->_generateRow($instance, $table);
162
			});
163
		if($table->getRowCount()==0){
164
			$result=$table->addRow();
165
			$result->mergeRow();
166
			$result->setValues([$this->_emptyMessage]);
167
		}
168
	}
169
170
	protected function _generateRow($instance,&$table,$checkedClass=null){
171
		$this->_instanceViewer->setInstance($instance);
172
		InstanceViewer::$index++;
173
		$values= $this->_instanceViewer->getValues();
174
		$id=$this->_instanceViewer->getIdentifier();
175
		if($this->_hasCheckboxes){
176
			$ck=new HtmlCheckbox("ck-".$this->identifier."-".$id,"");
177
			$ck->setOnChange("event.stopPropagation();");
178
			$field=$ck->getField();
179
			$field->setProperty("value",$id);
180
			$field->setProperty("name", "selection[]");
181
			if(isset($checkedClass))
182
				$field->setClass($checkedClass);
183
			\array_unshift($values, $ck);
184
		}
185
		$result=$table->newRow();
186
		$result->setIdentifier($this->identifier."-tr-".$id);
187
		$result->setProperty("data-ajax",$id);
188
		$result->setValues($values);
189
		$result->addToProperty("class",$this->_rowClass);
190
		return $result;
191
	}
192
193
	protected function _generatePagination($table,$js=NULL){
194
		if(isset($this->_toolbar)){
195
			if($this->_toolbarPosition==PositionInTable::FOOTER)
196
				$this->_toolbar->setFloated("left");
197
		}
198
		$footer=$table->getFooter();
199
		$footer->mergeCol();
200
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
201
		$menu->floatRight();
202
		$menu->setActiveItem($this->_pagination->getPage()-1);
203
		$footer->addValues($menu);
204
		$this->_associatePaginationBehavior($menu,$js);
205
	}
206
207
	protected function _associatePaginationBehavior(HtmlMenu $menu,JsUtils $js=NULL){
208
		if(isset($this->_urls["refresh"])){
209
			$menu->postOnClick($this->_urls["refresh"],"{'p':$(this).attr('data-page')}",$this->getRefreshSelector(),["preventDefault"=>false,"jqueryDone"=>"replaceWith","hasLoader"=>false]);
210
		}
211
	}
212
213
	protected function _getFieldName($index){
214
		$fieldName=parent::_getFieldName($index);
215
		if(\is_object($fieldName))
216
			$fieldName="field-".$index;
217
		return $fieldName."[]";
218
	}
219
220
	protected function _getFieldCaption($index){
221
		return null;
222
	}
223
224
	protected function _setToolbarPosition($table,$captions=NULL){
225
		switch ($this->_toolbarPosition){
226
			case PositionInTable::BEFORETABLE:
227
			case PositionInTable::AFTERTABLE:
228
				if(isset($this->_compileParts)===false){
229
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
230
				}
231
				break;
232
			case PositionInTable::HEADER:
233
			case PositionInTable::FOOTER:
234
			case PositionInTable::BODY:
235
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
236
				break;
237
		}
238
	}
239
240
	/**
241
	 * Associates a $callback function after the compilation of the field at $index position
242
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
243
	 * @param int $index postion of the compiled field
244
	 * @param callable $callback function called after the field compilation
245
	 * @return DataTable
246
	 */
247
	public function afterCompile($index,$callback){
248
		$this->_instanceViewer->afterCompile($index,$callback);
249
		return $this;
250
	}
251
252
	private function addToolbarRow($part,$table,$captions){
253
		$hasPart=$table->hasPart($part);
254
		if($hasPart){
255
			$row=$table->getPart($part)->addRow(\sizeof($captions));
256
		}else{
257
			$row=$table->getPart($part)->getRow(0);
258
		}
259
		$row->mergeCol();
260
		$row->setValues([$this->_toolbar]);
261
	}
262
263
	/**
264
	 * {@inheritDoc}
265
	 * @see Widget::getHtmlComponent()
266
	 * @return HtmlTable
267
	 */
268
	public function getHtmlComponent(){
269
		return $this->content["table"];
270
	}
271
272
	public function getUrls() {
273
		return $this->_urls;
274
	}
275
276
	/**
277
	 * Sets the associative array of urls for refreshing, updating or deleting
278
	 * think of defining the update zone with the setTargetSelector method
279
	 * @param string|array $urls associative array with keys refresh: for refreshing with search field or pagination, edit : for updating a row, delete: for deleting a row
280
	 * @return DataTable
281
	 */
282
	public function setUrls($urls) {
283
		if(\is_array($urls)){
284
			$this->_urls["refresh"]=JArray::getValue($urls, "refresh",0);
285
			$this->_urls["edit"]=JArray::getValue($urls, "edit",1);
286
			$this->_urls["delete"]=JArray::getValue($urls, "delete",2);
287
		}else{
288
			$this->_urls=["refresh"=>$urls,"edit"=>$urls,"delete"=>$urls];
289
		}
290
		return $this;
291
	}
292
293
	/**
294
	 * Paginates the DataTable element with a Semantic HtmlPaginationMenu component
295
	 * @param number $page the active page number
296
	 * @param number $total_rowcount the total number of items
297
	 * @param number $items_per_page The number of items per page
298
	 * @param number $pages_visibles The number of visible pages in the Pagination component
299
	 * @return DataTable
300
	 */
301
	public function paginate($page,$total_rowcount,$items_per_page=10,$pages_visibles=null){
302
		$this->_pagination=new Pagination($items_per_page,$pages_visibles,$page,$total_rowcount);
303
		return $this;
304
	}
305
306
	/**
307
	 * Auto Paginates the DataTable element with a Semantic HtmlPaginationMenu component
308
	 * @param number $page the active page number
309
	 * @param number $items_per_page The number of items per page
310
	 * @param number $pages_visibles The number of visible pages in the Pagination component
311
	 * @return DataTable
312
	 */
313
	public function autoPaginate($page=1,$items_per_page=10,$pages_visibles=4){
314
		$this->_pagination=new Pagination($items_per_page,$pages_visibles,$page);
315
		return $this;
316
	}
317
318
319
320
	/**
321
	 * @param array $compileParts
322
	 * @return DataTable
323
	 */
324
	public function refresh($compileParts=["tbody"]){
325
		$this->_compileParts=$compileParts;
326
		return $this;
327
	}
328
329
330
	/**
331
	 * Adds a search input in toolbar
332
	 * @param string $position
333
	 * @return \Ajax\common\html\HtmlDoubleElement
334
	 */
335
	public function addSearchInToolbar($position=Direction::RIGHT){
336
		return $this->addInToolbar($this->getSearchField())->setPosition($position);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Ajax\common\html\HtmlDoubleElement as the method setPosition() does only exist in the following sub-classes of Ajax\common\html\HtmlDoubleElement: Ajax\bootstrap\html\content\HtmlGridCol, Ajax\semantic\html\colle...menus\HtmlAccordionMenu, Ajax\semantic\html\collections\menus\HtmlIconMenu, Ajax\semantic\html\colle...nus\HtmlLabeledIconMenu, Ajax\semantic\html\collections\menus\HtmlMenu, Ajax\semantic\html\colle...enus\HtmlPaginationMenu, Ajax\semantic\html\content\HtmlAccordionMenuItem, Ajax\semantic\html\content\HtmlDropdownItem, Ajax\semantic\html\content\HtmlMenuItem, Ajax\semantic\html\modules\HtmlPopup. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
337
	}
338
339
	public function getSearchField(){
340
		if(isset($this->_searchField)===false){
341
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
342
			$this->_searchField->addIcon("search",Direction::RIGHT);
343
		}
344
		return $this->_searchField;
345
	}
346
347
	/**
348
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
349
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
350
	 * @param callable $callback
351
	 * @return DataTable
352
	 */
353
	public function onNewRow($callback) {
354
		$this->content["table"]->onNewRow($callback);
355
		return $this;
356
	}
357
358
	/**
359
	 * Returns a form corresponding to the Datatable
360
	 * @return \Ajax\semantic\html\collections\form\HtmlForm
361
	 */
362
	public function asForm(){
363
		return $this->getForm();
364
	}
365
366
367
368
	protected function getTargetSelector($op) {
369
		$result=$this->_targetSelector;
370
		if(!isset($result[$op]))
371
			$result="#".$this->identifier;
372
		return $result[$op];
373
	}
374
375
	/**
376
	 * Sets the response element selector for Edit and Delete request with ajax
377
	 * @param string|array $_targetSelector string or associative array ["edit"=>"edit_selector","delete"=>"delete_selector"]
378
	 * @return DataTable
379
	 */
380
	public function setTargetSelector($_targetSelector) {
381
		if(!\is_array($_targetSelector)){
382
			$_targetSelector=["edit"=>$_targetSelector,"delete"=>$_targetSelector];
383
		}
384
		$this->_targetSelector=$_targetSelector;
385
		return $this;
386
	}
387
388
	public function getRefreshSelector() {
389
		if(isset($this->_refreshSelector))
390
			return $this->_refreshSelector;
391
		return "#".$this->identifier." tbody";
392
	}
393
394
	/**
395
	 * @param string $_refreshSelector
396
	 * @return DataTable
397
	 */
398
	public function setRefreshSelector($_refreshSelector) {
399
		$this->_refreshSelector=$_refreshSelector;
400
		return $this;
401
	}
402
403
	/**
404
	 * {@inheritDoc}
405
	 * @see \Ajax\common\Widget::show()
406
	 */
407
	public function show($modelInstance){
408
		if(\is_array($modelInstance)){
409
			if(isset($modelInstance[0]) && \is_array(array_values($modelInstance)[0]))
410
				$modelInstance=\json_decode(\json_encode($modelInstance), FALSE);
411
		}
412
		$this->_modelInstance=$modelInstance;
413
	}
414
415
	public function getRowClass() {
416
		return $this->_rowClass;
417
	}
418
419
	/**
420
	 * Sets the default row class (tr class)
421
	 * @param string $_rowClass
422
	 * @return DataTable
423
	 */
424
	public function setRowClass($_rowClass) {
425
		$this->_rowClass=$_rowClass;
426
		return $this;
427
	}
428
429
	/**
430
	 * Sets the message displayed when there is no record
431
	 * @param mixed $_emptyMessage
432
	 * @return DataTable
433
	 */
434
	public function setEmptyMessage($_emptyMessage) {
435
		$this->_emptyMessage=$_emptyMessage;
436
		return $this;
437
	}
438
439
	public function setSortable($colIndex=NULL) {
440
		$this->_sortable=$colIndex;
441
		return $this;
442
	}
443
444
	public function setActiveRowSelector($class="active",$event="click",$multiple=false){
445
		$this->_self->setActiveRowSelector($class,$event,$multiple);
446
		return $this;
447
	}
448
449
	public function hideColumn($colIndex){
450
		if(!\is_array($this->_hiddenColumns))
451
			$this->_hiddenColumns=[];
452
		$this->_hiddenColumns[]=$colIndex;
453
		return $this;
454
	}
455
456
	public function setColWidth($colIndex,$width){
457
		$this->_colWidths[$colIndex]=$width;
458
		return $this;
459
	}
460
	public function setColWidths($_colWidths) {
461
		$this->_colWidths = $_colWidths;
462
		return $this;
463
	}
464
465
	public function setColAlignment($colIndex,$alignment){
466
		$this->content["table"]->setColAlignment($colIndex,$alignment);
467
		return $this;
468
	}
469
}
470