Completed
Push — master ( 3555a5...e61355 )
by Jean-Christophe
03:33
created

DataTable::addButtonInToolbar()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
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
16
class DataTable extends Widget {
17
18
	protected $_searchField;
19
	protected $_searchUrl;
20
	protected $_pagination;
21
	protected $_hasCheckboxes=true;
22
	protected $_toolbar;
23
	protected $_compileParts;
24
	protected $_toolbarPosition;
25
26
	public function run(JsUtils $js){
27
		if($this->_hasCheckboxes && isset($js)){
28
			$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
29
		var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
30
		\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
31
		if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');}");
32
		}
33
		parent::run($js);
34
	}
35
36
	public function __construct($identifier,$model,$modelInstance=NULL) {
37
		parent::__construct($identifier, $model,$modelInstance);
38
		$this->_instanceViewer=new InstanceViewer();
39
		$this->content=["table"=>new HtmlTable($identifier, 0,0)];
40
		$this->_toolbarPosition=PositionInTable::BEFORETABLE;
41
	}
42
43
	public function compile(JsUtils $js=NULL,&$view=NULL){
44
		if(isset($this->_toolbar) && isset($this->_compileParts)===false){
45
			if($this->_toolbarPosition===PositionInTable::BEFORETABLE){
46
				$this->content["before"]=$this->_toolbar;
47
			}elseif($this->_toolbarPosition===PositionInTable::BEFORETABLE){
48
				$this->content["after"]=$this->_toolbar;
49
			}
50
		}
51
52
		$this->_instanceViewer->setInstance($this->_model);
53
		$captions=$this->_instanceViewer->getCaptions();
54
55
		$table=$this->content["table"];
56
		//$table=new HtmlTable($identifier, 0, 0);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
57
		if($this->_hasCheckboxes){
58
			$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
59
			$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);");
60
			$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);");
61
			\array_unshift($captions, $ck);
62
		}
63
64
		$table->setRowCount(0, \sizeof($captions));
65
		$table->setHeaderValues($captions);
66
		if(isset($this->_compileParts))
67
			$table->setCompileParts($this->_compileParts);
68
		if(isset($this->_searchField)){
69
			if(isset($js))
70
				$this->_searchField->postOn("change", $this->_searchUrl,"{'s':$(this).val()}","-#".$this->identifier." tbody",["preventDefault"=>false]);
71
		}
72
73
		$objects=$this->_modelInstance;
74
		if(isset($this->_pagination)){
75
			$objects=$this->_pagination->getObjects($this->_modelInstance);
76
		}
77
		InstanceViewer::setIndex(0);
78
		$table->fromDatabaseObjects($objects, function($instance){
79
			$this->_instanceViewer->setInstance($instance);
80
			$result= $this->_instanceViewer->getValues();
81
			if($this->_hasCheckboxes){
82
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
83
				$field=$ck->getField();
84
				$field->setProperty("value",$this->_instanceViewer->getCkValue());
0 ignored issues
show
Bug introduced by
The method getCkValue does only exist in Ajax\semantic\widgets\datatable\InstanceViewer, but not in Ajax\semantic\widgets\InstanceViewer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
85
				$field->setProperty("name", "selection[]");
86
				\array_unshift($result, $ck);
87
			}
88
			return $result;
89
		});
90
			if($this->_hasCheckboxes){
91
				if($table->hasPart("thead"))
92
					$table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort");
93
			}
94
95
		if(isset($this->_pagination) && $this->_pagination->getVisible()){
96
			$footer=$table->getFooter();
97
			$footer->mergeCol();
98
			$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
99
			$menu->floatRight();
100
			$menu->setActiveItem($this->_pagination->getPage()-1);
101
			$footer->setValues($menu);
102
			$menu->postOnClick($this->_searchUrl,"{'p':$(this).attr('data-page')}","-#".$this->identifier." tbody",["preventDefault"=>false]);
103
		}
104
		if(isset($this->_toolbar)){
105
			if($this->_toolbarPosition===PositionInTable::FOOTER)
106
				$this->addToolbarRow("tfoot",$table, $captions);
107
			elseif($this->_toolbarPosition===PositionInTable::HEADER){
108
				$this->addToolbarRow("thead",$table, $captions);
109
			}
110
		}
111
		$this->content=JArray::sortAssociative($this->content, ["before","table","after"]);
112
		return parent::compile($js,$view);
113
	}
114
115
	private function addToolbarRow($part,$table,$captions){
116
		$row=$table->getPart($part)->addRow(\sizeof($captions));
117
		$row->mergeCol();
118
		$row->setValues([$this->_toolbar]);
119
	}
120
121
	public function getInstanceViewer() {
122
		return $this->_instanceViewer;
123
	}
124
125
	public function setInstanceViewer($_instanceViewer) {
126
		$this->_instanceViewer=$_instanceViewer;
127
		return $this;
128
	}
129
130
	public function setCaptions($captions){
131
		$this->_instanceViewer->setCaptions($captions);
132
		return $this;
133
	}
134
135
	public function setFields($fields){
136
		$this->_instanceViewer->setVisibleProperties($fields);
137
		return $this;
138
	}
139
140
	public function addField($field){
141
		$this->_instanceViewer->addField($field);
0 ignored issues
show
Bug introduced by
The method addField does only exist in Ajax\semantic\widgets\datatable\InstanceViewer, but not in Ajax\semantic\widgets\InstanceViewer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
142
		return $this;
143
	}
144
145
	public function insertField($index,$field){
146
		$this->_instanceViewer->insertField($index, $field);
0 ignored issues
show
Bug introduced by
The method insertField does only exist in Ajax\semantic\widgets\datatable\InstanceViewer, but not in Ajax\semantic\widgets\InstanceViewer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
147
		return $this;
148
	}
149
150
	public function insertInField($index,$field){
151
		$this->_instanceViewer->insertInField($index, $field);
0 ignored issues
show
Bug introduced by
The method insertInField does only exist in Ajax\semantic\widgets\datatable\InstanceViewer, but not in Ajax\semantic\widgets\InstanceViewer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
152
		return $this;
153
	}
154
155
	public function setValueFunction($index,$callback){
156
		$this->_instanceViewer->setValueFunction($index, $callback);
157
		return $this;
158
	}
159
160
	public function setCkValueFunction($callback){
161
		$this->_instanceViewer->setCkValueFunction($callback);
0 ignored issues
show
Bug introduced by
The method setCkValueFunction does only exist in Ajax\semantic\widgets\datatable\InstanceViewer, but not in Ajax\semantic\widgets\InstanceViewer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
162
		return $this;
163
	}
164
165
	public function getHtmlComponent(){
166
		return $this->content["table"];
167
	}
168
169
	public function getSearchUrl() {
170
		return $this->_searchUrl;
171
	}
172
173
	public function setSearchUrl($_searchUrl) {
174
		$this->_searchUrl=$_searchUrl;
175
		return $this;
176
	}
177
178
	public function paginate($items_per_page=10,$page=1){
179
		$this->_pagination=new Pagination($items_per_page,4,$page);
180
	}
181
182
	public function getHasCheckboxes() {
183
		return $this->_hasCheckboxes;
184
	}
185
186
	public function setHasCheckboxes($_hasCheckboxes) {
187
		$this->_hasCheckboxes=$_hasCheckboxes;
188
		return $this;
189
	}
190
191
	public function refresh($compileParts=["tbody"]){
192
		$this->_compileParts=$compileParts;
193
		return $this;
194
	}
195
	/**
196
	 * @param string $caption
197
	 * @param callable $callback
198
	 * @return callable
199
	 */
200
	private function getFieldButtonCallable($caption,$callback=null){
201
		return $this->getCallable($this->getFieldButton($caption),$callback);
202
	}
203
204
	/**
205
	 * @param mixed $object
206
	 * @param callable $callback
207
	 * @return callable
208
	 */
209
	private function getCallable($object,$callback=null){
210
		$result=function($instance) use($object,$callback){
211
			if(isset($callback)){
212
				if(\is_callable($callback)){
213
					$callback($object,$instance);
214
				}
215
			}
216
			return $object;
217
		};
218
		return $result;
219
	}
220
221
	/**
222
	 * @param string $caption
223
	 * @return HtmlButton
224
	 */
225
	private function getFieldButton($caption){
226
			$bt=new HtmlButton("",$caption);
227
			$bt->setProperty("data-ajax",$this->_instanceViewer->getCkValue());
0 ignored issues
show
Bug introduced by
The method getCkValue does only exist in Ajax\semantic\widgets\datatable\InstanceViewer, but not in Ajax\semantic\widgets\InstanceViewer.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
228
			return $bt;
229
	}
230
231
	/**
232
	 * Inserts a new Button for each row
233
	 * @param string $caption
234
	 * @param callable $callback
235
	 * @return \Ajax\semantic\widgets\datatable\DataTable
236
	 */
237
	public function addFieldButton($caption,$callback=null){
238
		$this->addField($this->getFieldButtonCallable($caption,$callback));
239
		return $this;
240
	}
241
242
	/**
243
	 * Inserts a new Button for each row at col $index
244
	 * @param int $index
245
	 * @param string $caption
246
	 * @param callable $callback
247
	 * @return \Ajax\semantic\widgets\datatable\DataTable
248
	 */
249
	public function insertFieldButton($index,$caption,$callback=null){
250
		$this->insertField($index, $this->getFieldButtonCallable($caption,$callback));
251
		return $this;
252
	}
253
254
	/**
255
	 * Inserts a new Button for each row in col at $index
256
	 * @param int $index
257
	 * @param string $caption
258
	 * @param callable $callback
259
	 * @return \Ajax\semantic\widgets\datatable\DataTable
260
	 */
261
	public function insertInFieldButton($index,$caption,$callback=null){
262
		$this->insertInField($index, $this->getFieldButtonCallable($caption,$callback));
263
		return $this;
264
	}
265
266
	private function addDefaultButton($icon,$class=null,$callback=null){
267
		$bt=$this->getDefaultButton($icon,$class);
268
		$this->addField($this->getCallable($bt,$callback));
269
		return $this;
270
	}
271
272
	private function insertDefaultButtonIn($index,$icon,$class=null,$callback=null){
273
		$bt=$this->getDefaultButton($icon,$class);
274
		$this->insertInField($index,$this->getCallable($bt,$callback));
275
		return $this;
276
	}
277
278
	private function getDefaultButton($icon,$class=null){
279
		$bt=$this->getFieldButton("");
280
		$bt->asIcon($icon);
281
		if(isset($class))
282
			$bt->addToProperty("class", $class);
283
		return $bt;
284
	}
285
286
	public function addDeleteButton($callback=null){
287
		return $this->addDefaultButton("remove","delete red basic",$callback);
288
	}
289
290
	public function addEditButton($callback=null){
291
		return $this->addDefaultButton("edit","edit basic",$callback);
292
	}
293
294
	public function insertDeleteButtonIn($index,$callback=null){
295
		return $this->insertDefaultButtonIn($index,"remove","delete red basic",$callback);
296
	}
297
298
	public function insertEditButtonIn($index,$callback=null){
299
		return $this->insertDefaultButtonIn($index,"edit","edit basic",$callback);
300
	}
301
302
	public function setSelectable(){
303
		$this->content["table"]->setSelectable();
304
		return $this;
305
	}
306
307
	/**
308
	 * @return \Ajax\semantic\html\collections\menus\HtmlMenu
309
	 */
310
	public function getToolbar(){
311
		if(isset($this->_toolbar)===false){
312
			$this->_toolbar=new HtmlMenu("toolbar-".$this->identifier);
313
			$this->_toolbar->setSecondary();
314
		}
315
		return $this->_toolbar;
316
	}
317
318
	/**
319
	 * @param unknown $element
320
	 * @return \Ajax\common\html\HtmlDoubleElement
321
	 */
322
	public function addInToolbar($element){
323
		$tb=$this->getToolbar();
324
		return $tb->addItem($element);
325
	}
326
327
	public function addItemInToolbar($caption,$icon=NULL){
328
		$result=$this->addInToolbar($caption);
329
		$result->addIcon($icon);
330
		return $result;
331
	}
332
333
	public function addButtonInToolbar($caption){
334
		$bt=new HtmlButton("",$caption);
335
		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...
336
	}
337
338
	public function addLabelledIconButtonInToolbar($caption,$icon,$before=true,$labeled=false){
339
		$bt=new HtmlButton("",$caption);
340
		$bt->addIcon($icon,$before,$labeled);
341
		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...
342
	}
343
344
345
	public function addSearchInToolbar(){
346
		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...
347
	}
348
349
	public function getSearchField(){
350
		if(isset($this->_searchField)===false){
351
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
352
			$this->_searchField->addIcon("search",Direction::RIGHT);
353
		}
354
		return $this->_searchField;
355
	}
356
357
	public function setSortable($colIndex=NULL) {
358
		$this->content["table"]->setSortable($colIndex);
359
		return $this;
360
	}
361
}