Completed
Push — master ( 72aa1a...163c98 )
by Jean-Christophe
03:34
created

DataTable::setUrls()   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\base\constants\Direction;
13
use Ajax\service\JArray;
14
use Ajax\semantic\html\base\HtmlSemDoubleElement;
15
use Ajax\semantic\widgets\base\InstanceViewer;
16
17
/**
18
 * DataTable widget for displaying list of objects
19
 * @version 1.0
20
 * @author jc
21
 * @since 2.2
22
 *
23
 */
24
class DataTable extends Widget {
25
26
	protected $_searchField;
27
	protected $_urls;
28
	protected $_pagination;
29
	protected $_hasCheckboxes;
30
	protected $_compileParts;
31
32
	public function run(JsUtils $js){
33
		if($this->_hasCheckboxes && isset($js)){
34
			$js->execOn("change", "#".$this->identifier." [name='selection[]']", "
35
		var \$parentCheckbox=\$('#ck-main-ck-{$this->identifier}'),\$checkbox=\$('#{$this->identifier} [name=\"selection[]\"]'),allChecked=true,allUnchecked=true;
36
		\$checkbox.each(function() {if($(this).prop('checked')){allUnchecked = false;}else{allChecked = false;}});
37
		if(allChecked) {\$parentCheckbox.checkbox('set checked');}else if(allUnchecked){\$parentCheckbox.checkbox('set unchecked');}else{\$parentCheckbox.checkbox('set indeterminate');}");
38
		}
39
		parent::run($js);
40
	}
41
42 View Code Duplication
	public function __construct($identifier,$model,$modelInstance=NULL) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
43
		parent::__construct($identifier, $model,$modelInstance);
44
		$this->_instanceViewer=new InstanceViewer();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Ajax\semantic\widgets\base\InstanceViewer() of type object<Ajax\semantic\widgets\base\InstanceViewer> is incompatible with the declared type object<Ajax\common\InstanceViewer> of property $_instanceViewer.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
45
		$this->content=["table"=>new HtmlTable($identifier, 0,0)];
46
		$this->_toolbarPosition=PositionInTable::BEFORETABLE;
47
	}
48
49
	public function compile(JsUtils $js=NULL,&$view=NULL){
50
		$this->_instanceViewer->setInstance($this->_model);
51
		$captions=$this->_instanceViewer->getCaptions();
52
53
		$table=$this->content["table"];
54
55
		if($this->_hasCheckboxes){
56
			$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
57
			$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);");
58
			$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);");
59
			\array_unshift($captions, $ck);
60
		}
61
62
		$table->setRowCount(0, \sizeof($captions));
63
		$table->setHeaderValues($captions);
64
		if(isset($this->_compileParts))
65
			$table->setCompileParts($this->_compileParts);
66
		if(isset($this->_searchField)){
67
			if(isset($js))
68
				$this->_searchField->postOn("change", $this->_urls,"{'s':$(this).val()}","-#".$this->identifier." tbody",["preventDefault"=>false]);
69
		}
70
71
		$this->_generateContent($table);
72
73
		if($this->_hasCheckboxes){
74
			if($table->hasPart("thead"))
75
				$table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort");
76
		}
77
78
		if(isset($this->_pagination) && $this->_pagination->getVisible()){
79
			$this->_generatePagination($table);
80
		}
81
		if(isset($this->_toolbar)){
82
			$this->_setToolbarPosition($table, $captions);
83
		}
84
		$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
85
		return parent::compile($js,$view);
86
	}
87
88
	protected function _generateContent($table){
89
		$objects=$this->_modelInstance;
90
		if(isset($this->_pagination)){
91
			$objects=$this->_pagination->getObjects($this->_modelInstance);
92
		}
93
		InstanceViewer::setIndex(0);
94
		$table->fromDatabaseObjects($objects, function($instance){
95
			$this->_instanceViewer->setInstance($instance);
96
			InstanceViewer::$index++;
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
	protected function _setToolbarPosition($table,$captions=NULL){
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, $instance : the active instance of the object, $index: the field position
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 getHtmlComponent(){
151
		return $this->content["table"];
152
	}
153
154
	public function getUrls() {
155
		return $this->_urls;
156
	}
157
158
	public function setUrls($urls) {
159
		$this->_urls=$urls;
160
		return $this;
161
	}
162
163
	public function paginate($items_per_page=10,$page=1){
164
		$this->_pagination=new Pagination($items_per_page,4,$page);
165
	}
166
167
	public function getHasCheckboxes() {
168
		return $this->_hasCheckboxes;
169
	}
170
171
	public function setHasCheckboxes($_hasCheckboxes) {
172
		$this->_hasCheckboxes=$_hasCheckboxes;
173
		return $this;
174
	}
175
176
	public function refresh($compileParts=["tbody"]){
177
		$this->_compileParts=$compileParts;
178
		return $this;
179
	}
180
	/**
181
	 * @param string $caption
182
	 * @param callable $callback
183
	 * @return callable
184
	 */
185
	private function getFieldButtonCallable($caption,$callback=null){
186
		return $this->getCallable("getFieldButton",[$caption],$callback);
187
	}
188
189
	/**
190
	 * @param mixed $object
0 ignored issues
show
Bug introduced by
There is no parameter named $object. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
191
	 * @param callable $callback
192
	 * @return callable
193
	 */
194
	private function getCallable($thisCallback,$parameters,$callback=null){
195
		$result=function($instance) use($thisCallback,$parameters,$callback){
196
			$object=call_user_func_array(array($this,$thisCallback), $parameters);
197
			if(isset($callback)){
198
				if(\is_callable($callback)){
199
					$callback($object,$instance);
200
				}
201
			}
202
			if($object instanceof HtmlSemDoubleElement){
203
				$object->setProperty("data-ajax",$this->_instanceViewer->getIdentifier());
204
			}
205
			return $object;
206
		};
207
		return $result;
208
	}
209
210
	/**
211
	 * @param string $caption
212
	 * @return HtmlButton
213
	 */
214
	private function getFieldButton($caption){
215
		return new HtmlButton("",$caption);
216
	}
217
218
	/**
219
	 * Inserts a new Button for each row
220
	 * @param string $caption
221
	 * @param callable $callback
222
	 * @return \Ajax\semantic\widgets\datatable\DataTable
223
	 */
224
	public function addFieldButton($caption,$callback=null){
225
		$this->addField($this->getCallable("getFieldButton",[$caption],$callback));
226
		return $this;
227
	}
228
229
	/**
230
	 * Inserts a new Button for each row at col $index
231
	 * @param int $index
232
	 * @param string $caption
233
	 * @param callable $callback
234
	 * @return \Ajax\semantic\widgets\datatable\DataTable
235
	 */
236
	public function insertFieldButton($index,$caption,$callback=null){
237
		$this->insertField($index, $this->getFieldButtonCallable($caption,$callback));
238
		return $this;
239
	}
240
241
	/**
242
	 * Inserts a new Button for each row in col at $index
243
	 * @param int $index
244
	 * @param string $caption
245
	 * @param callable $callback
246
	 * @return \Ajax\semantic\widgets\datatable\DataTable
247
	 */
248
	public function insertInFieldButton($index,$caption,$callback=null){
249
		$this->insertInField($index, $this->getFieldButtonCallable($caption,$callback));
250
		return $this;
251
	}
252
253
	private function addDefaultButton($icon,$class=null,$callback=null){
254
		$this->addField($this->getCallable("getDefaultButton",[$icon,$class],$callback));
255
		return $this;
256
	}
257
258
	private function insertDefaultButtonIn($index,$icon,$class=null,$callback=null){
259
		$this->insertInField($index,$this->getCallable("getDefaultButton",[$icon,$class],$callback));
260
		return $this;
261
	}
262
263
	private function getDefaultButton($icon,$class=null){
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
264
		$bt=$this->getFieldButton("");
265
		$bt->asIcon($icon);
266
		if(isset($class))
267
			$bt->addToProperty("class", $class);
268
		return $bt;
269
	}
270
271
	public function addDeleteButton($callback=null){
272
		return $this->addDefaultButton("remove","delete red basic",$callback);
273
	}
274
275
	public function addEditButton($callback=null){
276
		return $this->addDefaultButton("edit","edit basic",$callback);
277
	}
278
279
	public function addEditDeleteButtons($callbackEdit=null,$callbackDelete=null){
280
		$this->addEditButton($callbackEdit);
281
		$index=$this->_instanceViewer->visiblePropertiesCount()-1;
282
		$this->insertDeleteButtonIn($index,$callbackDelete);
283
		return $this;
284
	}
285
286
	public function insertDeleteButtonIn($index,$callback=null){
287
		return $this->insertDefaultButtonIn($index,"remove","delete red basic",$callback);
288
	}
289
290
	public function insertEditButtonIn($index,$callback=null){
291
		return $this->insertDefaultButtonIn($index,"edit","edit basic",$callback);
292
	}
293
294
	public function setSelectable(){
295
		$this->content["table"]->setSelectable();
296
		return $this;
297
	}
298
299
	public function addSearchInToolbar(){
300
		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...
301
	}
302
303
	public function getSearchField(){
304
		if(isset($this->_searchField)===false){
305
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
306
			$this->_searchField->addIcon("search",Direction::RIGHT);
307
		}
308
		return $this->_searchField;
309
	}
310
311
	public function setSortable($colIndex=NULL) {
312
		$this->content["table"]->setSortable($colIndex);
313
		return $this;
314
	}
315
316
	/**
317
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
318
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
319
	 * @param callable $callback
320
	 * @return DataTable
321
	 */
322
	public function onNewRow($callback) {
323
		$this->content["table"]->onNewRow($callback);
324
		return $this;
325
	}
326
}