Completed
Push — master ( b92773...4d236d )
by Jean-Christophe
03:15
created

DataTable::fieldAsInput()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 13
Ratio 100 %

Importance

Changes 0
Metric Value
dl 13
loc 13
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 9
nc 1
nop 4
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\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\elements\HtmlButton;
12
use Ajax\semantic\html\collections\menus\HtmlMenu;
13
use Ajax\semantic\html\base\constants\Direction;
14
use Ajax\service\JArray;
15
use Ajax\semantic\widgets\base\FieldAsTrait;
16
17
/**
18
 * DataTable widget for displaying list of objects
19
 * @author jc
20
 *
21
 */
22
class DataTable extends Widget {
23
	use FieldAsTrait;
24
25
	protected $_searchField;
26
	protected $_urls;
27
	protected $_pagination;
28
	protected $_hasCheckboxes;
29
	protected $_toolbar;
30
	protected $_compileParts;
31
	protected $_toolbarPosition;
32
33
	public function run(JsUtils $js){
34
		if($this->_hasCheckboxes && isset($js)){
35
			$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
36
		var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
37
		\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
38
		if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');}");
39
		}
40
		parent::run($js);
41
	}
42
43
	public function __construct($identifier,$model,$modelInstance=NULL) {
44
		parent::__construct($identifier, $model,$modelInstance);
45
		$this->_instanceViewer=new InstanceViewer();
46
		$this->content=["table"=>new HtmlTable($identifier, 0,0)];
47
		$this->_toolbarPosition=PositionInTable::BEFORETABLE;
48
	}
49
50
	public function compile(JsUtils $js=NULL,&$view=NULL){
51
		$this->_instanceViewer->setInstance($this->_model);
52
		$captions=$this->_instanceViewer->getCaptions();
53
54
		$table=$this->content["table"];
55
56
		if($this->_hasCheckboxes){
57
			$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
58
			$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);");
59
			$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);");
60
			\array_unshift($captions, $ck);
61
		}
62
63
		$table->setRowCount(0, \sizeof($captions));
64
		$table->setHeaderValues($captions);
65
		if(isset($this->_compileParts))
66
			$table->setCompileParts($this->_compileParts);
67
		if(isset($this->_searchField)){
68
			if(isset($js))
69
				$this->_searchField->postOn("change", $this->_urls,"{'s':$(this).val()}","-#".$this->identifier." tbody",["preventDefault"=>false]);
70
		}
71
72
		$this->_generateContent($table);
73
74
		if($this->_hasCheckboxes){
75
			if($table->hasPart("thead"))
76
				$table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort");
77
		}
78
79
		if(isset($this->_pagination) && $this->_pagination->getVisible()){
80
			$this->_generatePagination($table);
81
		}
82
		if(isset($this->_toolbar)){
83
			$this->_setToolbarPosition($table, $captions);
84
		}
85
		$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
86
		return parent::compile($js,$view);
87
	}
88
89
	private function _generateContent($table){
90
		$objects=$this->_modelInstance;
91
		if(isset($this->_pagination)){
92
			$objects=$this->_pagination->getObjects($this->_modelInstance);
93
		}
94
		InstanceViewer::setIndex(0);
95
		$table->fromDatabaseObjects($objects, function($instance){
96
			$this->_instanceViewer->setInstance($instance);
97
			$result= $this->_instanceViewer->getValues();
98
			if($this->_hasCheckboxes){
99
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
100
				$field=$ck->getField();
101
				$field->setProperty("value",$this->_instanceViewer->getIdentifier());
102
				$field->setProperty("name", "selection[]");
103
				\array_unshift($result, $ck);
104
			}
105
			return $result;
106
		});
107
	}
108
109
	private function _generatePagination($table){
110
		$footer=$table->getFooter();
111
		$footer->mergeCol();
112
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
113
		$menu->floatRight();
114
		$menu->setActiveItem($this->_pagination->getPage()-1);
115
		$footer->setValues($menu);
116
		$menu->postOnClick($this->_urls,"{'p':$(this).attr('data-page')}","-#".$this->identifier." tbody",["preventDefault"=>false]);
117
	}
118
119
	private function _setToolbarPosition($table,$captions){
120
		switch ($this->_toolbarPosition){
121
			case PositionInTable::BEFORETABLE:case PositionInTable::AFTERTABLE:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
122
				if(isset($this->_compileParts)===false){
123
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
124
				}
125
				break;
126
			case PositionInTable::HEADER:case PositionInTable::FOOTER: case PositionInTable::BODY:
0 ignored issues
show
Coding Style introduced by
The case body in a switch statement must start on the line following the statement.

According to the PSR-2, the body of a case statement must start on the line immediately following the case statement.

switch ($expr) {
case "A":
    doSomething(); //right
    break;
case "B":

    doSomethingElse(); //wrong
    break;

}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
127
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
128
				break;
129
		}
130
	}
131
132
	/**
133
	 * Associates a $callback function after the compilation of the field at $index position
134
	 * The $callback function can take the following arguments : $field=>the compiled field, $index: the field position, $instance : the active instance of the object
135
	 * @param int $index postion of the compiled field
136
	 * @param callable $callback function called after the field compilation
137
	 * @return \Ajax\semantic\widgets\datatable\DataTable
138
	 */
139
	public function afterCompile($index,$callback){
140
		$this->_instanceViewer->afterCompile($index,$callback);
141
		return $this;
142
	}
143
144
	private function addToolbarRow($part,$table,$captions){
145
		$row=$table->getPart($part)->addRow(\sizeof($captions));
146
		$row->mergeCol();
147
		$row->setValues([$this->_toolbar]);
148
	}
149
150
	public function getInstanceViewer() {
151
		return $this->_instanceViewer;
152
	}
153
154
	public function setInstanceViewer($_instanceViewer) {
155
		$this->_instanceViewer=$_instanceViewer;
156
		return $this;
157
	}
158
159
	public function setCaptions($captions){
160
		$this->_instanceViewer->setCaptions($captions);
161
		return $this;
162
	}
163
164
	public function setFields($fields){
165
		$this->_instanceViewer->setVisibleProperties($fields);
166
		return $this;
167
	}
168
169
	public function addField($field){
170
		$this->_instanceViewer->addField($field);
171
		return $this;
172
	}
173
174
	public function insertField($index,$field){
175
		$this->_instanceViewer->insertField($index, $field);
176
		return $this;
177
	}
178
179
	public function insertInField($index,$field){
180
		$this->_instanceViewer->insertInField($index, $field);
181
		return $this;
182
	}
183
184
	public function setValueFunction($index,$callback){
185
		$this->_instanceViewer->setValueFunction($index, $callback);
186
		return $this;
187
	}
188
189
	public function setIdentifierFunction($callback){
190
		$this->_instanceViewer->setIdentifierFunction($callback);
191
		return $this;
192
	}
193
194
	public function getHtmlComponent(){
195
		return $this->content["table"];
196
	}
197
198
	public function getUrls() {
199
		return $this->_urls;
200
	}
201
202
	public function setUrls($urls) {
203
		$this->_urls=$urls;
204
		return $this;
205
	}
206
207
	public function paginate($items_per_page=10,$page=1){
208
		$this->_pagination=new Pagination($items_per_page,4,$page);
209
	}
210
211
	public function getHasCheckboxes() {
212
		return $this->_hasCheckboxes;
213
	}
214
215
	public function setHasCheckboxes($_hasCheckboxes) {
216
		$this->_hasCheckboxes=$_hasCheckboxes;
217
		return $this;
218
	}
219
220
	public function refresh($compileParts=["tbody"]){
221
		$this->_compileParts=$compileParts;
222
		return $this;
223
	}
224
	/**
225
	 * @param string $caption
226
	 * @param callable $callback
227
	 * @return callable
228
	 */
229
	private function getFieldButtonCallable($caption,$callback=null){
230
		return $this->getCallable($this->getFieldButton($caption),$callback);
231
	}
232
233
	/**
234
	 * @param mixed $object
235
	 * @param callable $callback
236
	 * @return callable
237
	 */
238
	private function getCallable($object,$callback=null){
239
		$result=function($instance) use($object,$callback){
240
			if(isset($callback)){
241
				if(\is_callable($callback)){
242
					$callback($object,$instance);
243
				}
244
			}
245
			return $object;
246
		};
247
		return $result;
248
	}
249
250
	/**
251
	 * @param string $caption
252
	 * @return HtmlButton
253
	 */
254
	private function getFieldButton($caption){
255
			$bt=new HtmlButton("",$caption);
256
			$bt->setProperty("data-ajax",$this->_instanceViewer->getIdentifier());
257
			return $bt;
258
	}
259
260
	/**
261
	 * Inserts a new Button for each row
262
	 * @param string $caption
263
	 * @param callable $callback
264
	 * @return \Ajax\semantic\widgets\datatable\DataTable
265
	 */
266
	public function addFieldButton($caption,$callback=null){
267
		$this->addField($this->getFieldButtonCallable($caption,$callback));
268
		return $this;
269
	}
270
271
	/**
272
	 * Inserts a new Button for each row at col $index
273
	 * @param int $index
274
	 * @param string $caption
275
	 * @param callable $callback
276
	 * @return \Ajax\semantic\widgets\datatable\DataTable
277
	 */
278
	public function insertFieldButton($index,$caption,$callback=null){
279
		$this->insertField($index, $this->getFieldButtonCallable($caption,$callback));
280
		return $this;
281
	}
282
283
	/**
284
	 * Inserts a new Button for each row in col at $index
285
	 * @param int $index
286
	 * @param string $caption
287
	 * @param callable $callback
288
	 * @return \Ajax\semantic\widgets\datatable\DataTable
289
	 */
290
	public function insertInFieldButton($index,$caption,$callback=null){
291
		$this->insertInField($index, $this->getFieldButtonCallable($caption,$callback));
292
		return $this;
293
	}
294
295
	private function addDefaultButton($icon,$class=null,$callback=null){
296
		$bt=$this->getDefaultButton($icon,$class);
297
		$this->addField($this->getCallable($bt,$callback));
298
		return $this;
299
	}
300
301
	private function insertDefaultButtonIn($index,$icon,$class=null,$callback=null){
302
		$bt=$this->getDefaultButton($icon,$class);
303
		$this->insertInField($index,$this->getCallable($bt,$callback));
304
		return $this;
305
	}
306
307
	private function getDefaultButton($icon,$class=null){
308
		$bt=$this->getFieldButton("");
309
		$bt->asIcon($icon);
310
		if(isset($class))
311
			$bt->addToProperty("class", $class);
312
		return $bt;
313
	}
314
315
	public function addDeleteButton($callback=null){
316
		return $this->addDefaultButton("remove","delete red basic",$callback);
317
	}
318
319
	public function addEditButton($callback=null){
320
		return $this->addDefaultButton("edit","edit basic",$callback);
321
	}
322
323
	public function addEditDeleteButtons($callbackEdit=null,$callbackDelete=null){
324
		$this->addEditButton($callbackEdit);
325
		$index=$this->_instanceViewer->visiblePropertiesCount()-1;
326
		$this->insertDeleteButtonIn($index,$callbackDelete);
327
		return $this;
328
	}
329
330
	public function insertDeleteButtonIn($index,$callback=null){
331
		return $this->insertDefaultButtonIn($index,"remove","delete red basic",$callback);
332
	}
333
334
	public function insertEditButtonIn($index,$callback=null){
335
		return $this->insertDefaultButtonIn($index,"edit","edit basic",$callback);
336
	}
337
338
	public function setSelectable(){
339
		$this->content["table"]->setSelectable();
340
		return $this;
341
	}
342
343
	/**
344
	 * @return \Ajax\semantic\html\collections\menus\HtmlMenu
345
	 */
346
	public function getToolbar(){
347
		if(isset($this->_toolbar)===false){
348
			$this->_toolbar=new HtmlMenu("toolbar-".$this->identifier);
349
			$this->_toolbar->setSecondary();
350
		}
351
		return $this->_toolbar;
352
	}
353
354
	/**
355
	 * @param unknown $element
356
	 * @return \Ajax\common\html\HtmlDoubleElement
357
	 */
358
	public function addInToolbar($element){
359
		$tb=$this->getToolbar();
360
		return $tb->addItem($element);
361
	}
362
363
	public function addItemInToolbar($caption,$icon=NULL){
364
		$result=$this->addInToolbar($caption);
365
		$result->addIcon($icon);
366
		return $result;
367
	}
368
369
	public function addButtonInToolbar($caption){
370
		$bt=new HtmlButton("",$caption);
371
		return $this->addInToolbar($bt);
0 ignored issues
show
Documentation introduced by
$bt is of type object<Ajax\semantic\html\elements\HtmlButton>, but the function expects a object<Ajax\semantic\widgets\datatable\unknown>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
372
	}
373
374
	public function addLabelledIconButtonInToolbar($caption,$icon,$before=true,$labeled=false){
375
		$bt=new HtmlButton("",$caption);
376
		$bt->addIcon($icon,$before,$labeled);
377
		return $this->addInToolbar($bt);
0 ignored issues
show
Documentation introduced by
$bt is of type object<Ajax\semantic\html\elements\HtmlButton>, but the function expects a object<Ajax\semantic\widgets\datatable\unknown>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
378
	}
379
380
381
	public function addSearchInToolbar(){
382
		return $this->addInToolbar($this->getSearchField())->setPosition("right");
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...
383
	}
384
385
	public function getSearchField(){
386
		if(isset($this->_searchField)===false){
387
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
388
			$this->_searchField->addIcon("search",Direction::RIGHT);
389
		}
390
		return $this->_searchField;
391
	}
392
393
	public function setSortable($colIndex=NULL) {
394
		$this->content["table"]->setSortable($colIndex);
395
		return $this;
396
	}
397
398
	protected function _getFieldIdentifier($prefix){
399
		return $this->identifier."-{$prefix}-".$this->_instanceViewer->getIdentifier();
400
	}
401
}