Completed
Push — master ( f4e97d...0ed595 )
by Jean-Christophe
03:13
created

DataTable::_generateMainCheckbox()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
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\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\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
use Ajax\semantic\html\collections\table\traits\TableTrait;
17
18
/**
19
 * DataTable widget for displaying list of objects
20
 * @version 1.0
21
 * @author jc
22
 * @since 2.2
23
 *
24
 */
25
class DataTable extends Widget {
26
	use TableTrait;
27
	protected $_searchField;
28
	protected $_urls;
29
	protected $_pagination;
30
	protected $_hasCheckboxes;
31
	protected $_compileParts;
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 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...
44
		parent::__construct($identifier, $model,$modelInstance);
45
		$this->_instanceViewer=new InstanceViewer($identifier);
46
		$this->content=["table"=>new HtmlTable($identifier, 0,0)];
47
		$this->_toolbarPosition=PositionInTable::BEFORETABLE;
48
	}
49
50
	/**
51
	 * {@inheritDoc}
52
	 * @see \Ajax\semantic\html\collections\table\TableTrait::getTable()
53
	 */
54
	protected function getTable() {
55
		return $this->content["table"];
56
	}
57
58
59
	public function compile(JsUtils $js=NULL,&$view=NULL){
60
		$this->_instanceViewer->setInstance($this->_model);
61
		$captions=$this->_instanceViewer->getCaptions();
62
63
		$table=$this->content["table"];
64
65
		if($this->_hasCheckboxes){
66
			$this->_generateMainCheckbox($captions);
67
		}
68
69
		$table->setRowCount(0, \sizeof($captions));
70
		$table->setHeaderValues($captions);
71
		if(isset($this->_compileParts))
72
			$table->setCompileParts($this->_compileParts);
73
		if(isset($this->_searchField) && isset($js)){
74
			$this->_searchField->postOn("change", $this->_urls,"{'s':$(this).val()}","-#".$this->identifier." tbody",["preventDefault"=>false]);
75
		}
76
77
		$this->_generateContent($table);
78
79
		if($this->_hasCheckboxes && $table->hasPart("thead")){
80
				$table->getHeader()->getCell(0, 0)->addToProperty("class","no-sort");
81
		}
82
83
		if(isset($this->_pagination) && $this->_pagination->getVisible()){
84
			$this->_generatePagination($table);
85
		}
86
		if(isset($this->_toolbar)){
87
			$this->_setToolbarPosition($table, $captions);
88
		}
89
		$this->content=JArray::sortAssociative($this->content, [PositionInTable::BEFORETABLE,"table",PositionInTable::AFTERTABLE]);
90
		return parent::compile($js,$view);
91
	}
92
93
	private function _generateMainCheckbox(&$captions){
94
		$ck=new HtmlCheckbox("main-ck-".$this->identifier,"");
95
		$ck->setOnChecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',true);");
96
		$ck->setOnUnchecked("$('#".$this->identifier." [name=%quote%selection[]%quote%]').prop('checked',false);");
97
		\array_unshift($captions, $ck);
98
	}
99
100
	protected function _generateContent($table){
101
		$objects=$this->_modelInstance;
102
		if(isset($this->_pagination)){
103
			$objects=$this->_pagination->getObjects($this->_modelInstance);
104
		}
105
		InstanceViewer::setIndex(0);
106
		$table->fromDatabaseObjects($objects, function($instance){
107
			$this->_instanceViewer->setInstance($instance);
108
			InstanceViewer::$index++;
109
			$result= $this->_instanceViewer->getValues();
110
			if($this->_hasCheckboxes){
111
				$ck=new HtmlCheckbox("ck-".$this->identifier,"");
112
				$field=$ck->getField();
113
				$field->setProperty("value",$this->_instanceViewer->getIdentifier());
114
				$field->setProperty("name", "selection[]");
115
				\array_unshift($result, $ck);
116
			}
117
			return $result;
118
		});
119
	}
120
121
	private function _generatePagination($table){
122
		$footer=$table->getFooter();
123
		$footer->mergeCol();
124
		$menu=new HtmlPaginationMenu("pagination-".$this->identifier,$this->_pagination->getPagesNumbers());
125
		$menu->floatRight();
126
		$menu->setActiveItem($this->_pagination->getPage()-1);
127
		$footer->setValues($menu);
128
		$menu->postOnClick($this->_urls,"{'p':$(this).attr('data-page')}","-#".$this->identifier." tbody",["preventDefault"=>false]);
129
	}
130
131
	protected function _setToolbarPosition($table,$captions=NULL){
132
		switch ($this->_toolbarPosition){
133
			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...
134
				if(isset($this->_compileParts)===false){
135
					$this->content[$this->_toolbarPosition]=$this->_toolbar;
136
				}
137
				break;
138
			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...
139
				$this->addToolbarRow($this->_toolbarPosition,$table, $captions);
140
				break;
141
		}
142
	}
143
144
	/**
145
	 * Associates a $callback function after the compilation of the field at $index position
146
	 * The $callback function can take the following arguments : $field=>the compiled field, $instance : the active instance of the object, $index: the field position
147
	 * @param int $index postion of the compiled field
148
	 * @param callable $callback function called after the field compilation
149
	 * @return \Ajax\semantic\widgets\datatable\DataTable
150
	 */
151
	public function afterCompile($index,$callback){
152
		$this->_instanceViewer->afterCompile($index,$callback);
153
		return $this;
154
	}
155
156
	private function addToolbarRow($part,$table,$captions){
157
		$row=$table->getPart($part)->addRow(\sizeof($captions));
158
		$row->mergeCol();
159
		$row->setValues([$this->_toolbar]);
160
	}
161
162
	public function getHtmlComponent(){
163
		return $this->content["table"];
164
	}
165
166
	public function getUrls() {
167
		return $this->_urls;
168
	}
169
170
	public function setUrls($urls) {
171
		$this->_urls=$urls;
172
		return $this;
173
	}
174
175
	public function paginate($items_per_page=10,$page=1){
176
		$this->_pagination=new Pagination($items_per_page,4,$page);
177
	}
178
179
	public function getHasCheckboxes() {
180
		return $this->_hasCheckboxes;
181
	}
182
183
	public function setHasCheckboxes($_hasCheckboxes) {
184
		$this->_hasCheckboxes=$_hasCheckboxes;
185
		return $this;
186
	}
187
188
	public function refresh($compileParts=["tbody"]){
189
		$this->_compileParts=$compileParts;
190
		return $this;
191
	}
192
	/**
193
	 * @param string $caption
194
	 * @param callable $callback
195
	 * @return callable
196
	 */
197
	private function getFieldButtonCallable($caption,$callback=null){
198
		return $this->getCallable("getFieldButton",[$caption],$callback);
199
	}
200
201
	/**
202
	 * @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...
203
	 * @param callable $callback
204
	 * @return callable
205
	 */
206
	private function getCallable($thisCallback,$parameters,$callback=null){
207
		$result=function($instance) use($thisCallback,$parameters,$callback){
208
			$object=call_user_func_array(array($this,$thisCallback), $parameters);
209
			if(isset($callback)){
210
				if(\is_callable($callback)){
211
					$callback($object,$instance);
212
				}
213
			}
214
			if($object instanceof HtmlSemDoubleElement){
215
				$object->setProperty("data-ajax",$this->_instanceViewer->getIdentifier());
216
			}
217
			return $object;
218
		};
219
		return $result;
220
	}
221
222
	/**
223
	 * @param string $caption
224
	 * @return HtmlButton
225
	 */
226
	private function getFieldButton($caption){
227
		return new HtmlButton("",$caption);
228
	}
229
230
	/**
231
	 * Inserts a new Button for each row
232
	 * @param string $caption
233
	 * @param callable $callback
234
	 * @return \Ajax\semantic\widgets\datatable\DataTable
235
	 */
236
	public function addFieldButton($caption,$callback=null){
237
		$this->addField($this->getCallable("getFieldButton",[$caption],$callback));
238
		return $this;
239
	}
240
241
	/**
242
	 * Inserts a new Button for each row at col $index
243
	 * @param int $index
244
	 * @param string $caption
245
	 * @param callable $callback
246
	 * @return \Ajax\semantic\widgets\datatable\DataTable
247
	 */
248
	public function insertFieldButton($index,$caption,$callback=null){
249
		$this->insertField($index, $this->getFieldButtonCallable($caption,$callback));
250
		return $this;
251
	}
252
253
	/**
254
	 * Inserts a new Button for each row in col at $index
255
	 * @param int $index
256
	 * @param string $caption
257
	 * @param callable $callback
258
	 * @return \Ajax\semantic\widgets\datatable\DataTable
259
	 */
260
	public function insertInFieldButton($index,$caption,$callback=null){
261
		$this->insertInField($index, $this->getFieldButtonCallable($caption,$callback));
262
		return $this;
263
	}
264
265
	private function addDefaultButton($icon,$class=null,$callback=null){
266
		$this->addField($this->getCallable("getDefaultButton",[$icon,$class],$callback));
267
		return $this;
268
	}
269
270
	private function insertDefaultButtonIn($index,$icon,$class=null,$callback=null){
271
		$this->insertInField($index,$this->getCallable("getDefaultButton",[$icon,$class],$callback));
272
		return $this;
273
	}
274
275
	private function getDefaultButton($icon,$class=null){
276
		$bt=$this->getFieldButton("");
277
		$bt->asIcon($icon);
278
		if(isset($class))
279
			$bt->addToProperty("class", $class);
280
		return $bt;
281
	}
282
283
	public function addDeleteButton($callback=null){
284
		return $this->addDefaultButton("remove","delete red basic",$callback);
285
	}
286
287
	public function addEditButton($callback=null){
288
		return $this->addDefaultButton("edit","edit basic",$callback);
289
	}
290
291
	public function addEditDeleteButtons($callbackEdit=null,$callbackDelete=null){
292
		$this->addEditButton($callbackEdit);
293
		$index=$this->_instanceViewer->visiblePropertiesCount()-1;
294
		$this->insertDeleteButtonIn($index,$callbackDelete);
295
		return $this;
296
	}
297
298
	public function insertDeleteButtonIn($index,$callback=null){
299
		return $this->insertDefaultButtonIn($index,"remove","delete red basic",$callback);
300
	}
301
302
	public function insertEditButtonIn($index,$callback=null){
303
		return $this->insertDefaultButtonIn($index,"edit","edit basic",$callback);
304
	}
305
306
	public function addSearchInToolbar($position=Direction::RIGHT){
307
		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...
308
	}
309
310
	public function getSearchField(){
311
		if(isset($this->_searchField)===false){
312
			$this->_searchField=new HtmlInput("search-".$this->identifier,"search","","Search...");
313
			$this->_searchField->addIcon("search",Direction::RIGHT);
314
		}
315
		return $this->_searchField;
316
	}
317
318
	/**
319
	 * The callback function called after the insertion of each row when fromDatabaseObjects is called
320
	 * callback function takes the parameters $row : the row inserted and $object: the instance of model used
321
	 * @param callable $callback
322
	 * @return DataTable
323
	 */
324
	public function onNewRow($callback) {
325
		$this->content["table"]->onNewRow($callback);
326
		return $this;
327
	}
328
}